first commit
This commit is contained in:
32
__play.js
Normal file
32
__play.js
Normal file
@@ -0,0 +1,32 @@
|
||||
const cookies = require('./cookie.json')
|
||||
const request = require('request')
|
||||
|
||||
let data = ''
|
||||
let csrf = ''
|
||||
cookies.forEach(cookie => {
|
||||
if (cookie.name == 'csrf') csrf = cookie.value
|
||||
if (cookie.name == 'csrf') console.log(cookie.value)
|
||||
Object.keys(cookie).forEach(key => {
|
||||
if (key == 'name') return
|
||||
data += cookie.name + '=' + cookie.value
|
||||
data += ';'
|
||||
})
|
||||
})
|
||||
data += 'x-amzn-dat-gui-client-v=1.24.2445.0;'
|
||||
|
||||
let headers = {
|
||||
'Pragma': 'no-cache',
|
||||
'Accept-Encoding': 'identity',
|
||||
'Accept-Language': 'de',
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:27.0) Gecko/20100101 Firefox/27.0',
|
||||
'Accept': 'application/json, text/javascript, */*; q=0.01',
|
||||
'Cache-Control': 'no-cache',
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'Connection': 'keep-alive',
|
||||
'Referer': 'https://layla.amazon.de/spa/index.html',
|
||||
'csrf': csrf,
|
||||
'Cookie': data
|
||||
}
|
||||
console.log(headers)
|
||||
|
||||
request.post({headers, url: 'https://layla.amazon.de/api/tunein/queue-and-play?deviceSerialNumber=G090LV03644201N0&deviceType=AB72C64C86AW2&guideId=s56049&contentType=station&callSign=&mediaOwnerCustomerId=A15ZKA34OGFDCC'}, (error, response, body) => console.log(error, response.headers, body))
|
||||
103
__websocket.js
Normal file
103
__websocket.js
Normal file
@@ -0,0 +1,103 @@
|
||||
const cookies = require('/Users/asciidisco/.alexis/cookies.json')
|
||||
const request = require('request')
|
||||
const WebSocket = require('faye-websocket')
|
||||
|
||||
let DEVICE_SERIAL = ''
|
||||
let DEVICE_TYPE = null
|
||||
let data = ''
|
||||
let is_connected = false
|
||||
|
||||
const do_connect = (url, device_type, headers) => {
|
||||
is_connected = true
|
||||
let ws = new WebSocket.Client(url)
|
||||
|
||||
ws.on('open', function(event) {
|
||||
console.log('open');
|
||||
});
|
||||
|
||||
ws.on('message', function(event) {
|
||||
console.log('message', event.data.toString())
|
||||
});
|
||||
|
||||
ws.on('close', function(event) {
|
||||
console.log('close', event.code, event.reason)
|
||||
ws = null;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
cookies.forEach(cookie => {
|
||||
if (cookie.name === 'ubid-acbde') DEVICE_SERIAL = cookie.value
|
||||
Object.keys(cookie).forEach(key => {
|
||||
if (key == 'name') return
|
||||
data += key == 'value' ? cookie.name + '=' + cookie.value : key + '=' + cookie[key]
|
||||
data += ';'
|
||||
})
|
||||
})
|
||||
|
||||
DEVICE_SERIAL += '-' + Date.now()
|
||||
|
||||
let http_headers = {
|
||||
'Pragma': 'no-cache',
|
||||
'Accept-Encoding': 'identity',
|
||||
'Accept-Language': 'de',
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:27.0) Gecko/20100101 Firefox/27.0',
|
||||
'Accept': 'application/json, text/javascript, */*; q=0.01',
|
||||
'Cache-Control': 'no-cache',
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'Connection': 'keep-alive',
|
||||
'Referer': 'https://layla.amazon.de/spa/index.html',
|
||||
'Cookie': data,
|
||||
}
|
||||
|
||||
let ws_headers = {
|
||||
'Accept-Language': 'de',
|
||||
'Referer': 'https://layla.amazon.de/spa/index.html',
|
||||
'Cookie': data,
|
||||
'Host': 'dp-gw-na.amazon.de',
|
||||
'Origin': 'https://layla.amazon.de',
|
||||
'Pragma': 'no-cache',
|
||||
'Accept-Encoding': 'identity',
|
||||
'Upgrade': 'websocket',
|
||||
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36',
|
||||
}
|
||||
|
||||
request({headers: http_headers, url: 'https://layla.amazon.de/api/cards?limit=100'}, (err, res, body) => {
|
||||
const cards = JSON.parse(body)
|
||||
cards.cards.forEach(card => {
|
||||
const parts = card.id.split('#')
|
||||
if (parts.pop() == 'BOGUS') {
|
||||
DEVICE_TYPE = parts[2]
|
||||
if (!is_connected) do_connect(`wss://dp-gw-na.amazon.de/?x-amz-device-type=${DEVICE_TYPE}&x-amz-device-serial=${DEVICE_SERIAL}`, DEVICE_TYPE, ws_headers)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
/*var ws = new WebSocket.Client('wss://dp-gw-na.amazon.de/?x-amz-device-type={DEVICE_TYPE}&x-amz-device-serial={DEVICE_SERIAL}', [], {
|
||||
proxy: {
|
||||
origin: 'https://layla.amazon.de',
|
||||
headers: {'User-Agent': 'node'}
|
||||
}
|
||||
})*/
|
||||
|
||||
/*client.on('connectFailed', function(error) {
|
||||
console.log('Connect Error: ' + error.toString());
|
||||
});
|
||||
|
||||
client.on('connect', function(connection) {
|
||||
console.log('WebSocket Client Connected');
|
||||
connection.on('error', function(error) {
|
||||
console.log("Connection Error: " + error.toString());
|
||||
});
|
||||
connection.on('close', function() {
|
||||
console.log('echo-protocol Connection Closed');
|
||||
});
|
||||
connection.on('message', function(message) {
|
||||
if (message.type === 'utf8') {
|
||||
console.log("Received: '" + message.utf8Data + "'");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
client.connect('wss://dp-gw-na.amazon.de/?x-amz-device-type=ALEGCNGL9K0HM&x-amz-device-serial=260-2954207-0537040-1503305987401', null, ['https://layla.amazon.de', headers]);
|
||||
setInterval(function () {}, 1000)*/
|
||||
16
bin/alexis
Executable file
16
bin/alexis
Executable file
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env node
|
||||
'use strict'
|
||||
var cli = require('../lib/cli')
|
||||
var logger = require('../lib/logger')
|
||||
|
||||
// checking node version
|
||||
if (parseInt(process.version.replace(/\./g, '').replace('v', ''), 10) < 600) {
|
||||
console.log('Node version is not sufficient, must be Node 6 or higher')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
cli(function (flags, showHelp) {
|
||||
var init = require('../lib/init')(logger)
|
||||
var result = init.run(flags, showHelp)
|
||||
if (result === false) process.exit(1)
|
||||
})
|
||||
35
lib/cli.js
Normal file
35
lib/cli.js
Normal file
@@ -0,0 +1,35 @@
|
||||
'use strict'
|
||||
var meow = require('meow')
|
||||
|
||||
var help = '' +
|
||||
'Authenticates against the Amazon Echo Web App API.\n' +
|
||||
'Spins up a webserver (and optionally a telnet server) to access your Echo devices for remote control.\n\n' +
|
||||
'Usage:\n' +
|
||||
' $ alexis [options]\n\n' +
|
||||
'Example:\n' +
|
||||
' $ alexis --http-port=5000 --email=my@mail.com --pass=MyHopefullySuperSecretAmazonPasswort\n\n' +
|
||||
'Options:\n' +
|
||||
' --email Your Echo account e-mail\n' +
|
||||
' --pass Your Echo account password\n' +
|
||||
' --http-port [optional] Port the HTTP Server should be running on\n' +
|
||||
' --telnet-port [optional] Port the Telnet Server should be running on\n' +
|
||||
' --interval [optional] The fetch interval for querying the Echo API (default 10 sec.)\n' +
|
||||
' --no-colors [optional] Disables all colors in the terminal outoput\n' +
|
||||
' --quiet [optional] Disables all terminal output\n'
|
||||
|
||||
var args = {
|
||||
string: [
|
||||
'http-port',
|
||||
'telnet-port',
|
||||
'email',
|
||||
'pass',
|
||||
'interval',
|
||||
'no-colors',
|
||||
'quiet',
|
||||
]
|
||||
}
|
||||
|
||||
module.exports = function (runner) {
|
||||
var cli = meow(help, args)
|
||||
runner(cli.flags, cli.showHelp)
|
||||
}
|
||||
21
lib/components/all/route.js
Normal file
21
lib/components/all/route.js
Normal file
@@ -0,0 +1,21 @@
|
||||
'use strict'
|
||||
|
||||
const all = (store, params, cb) => {
|
||||
let state = store.getState()
|
||||
cb({
|
||||
todos: state.todos,
|
||||
//shopping_list: state.shopping_list.items,
|
||||
//timeline: state.timeline.items,
|
||||
//network: state.network.network,
|
||||
//devices: state.devices.devices.devices,
|
||||
//household: state.household.household,
|
||||
//notifications: state.notifications.notifications.notifications,
|
||||
//user_data: state.user_data.userData.authentication,
|
||||
//wakewords: state.wakewords.wakewords,
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
'/': all,
|
||||
'/all': all,
|
||||
}
|
||||
5
lib/components/devices/action_constants.js
Normal file
5
lib/components/devices/action_constants.js
Normal file
@@ -0,0 +1,5 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = {
|
||||
RECEIVE_DEVICES: 'RECEIVE_DEVICES',
|
||||
}
|
||||
12
lib/components/devices/actions.js
Normal file
12
lib/components/devices/actions.js
Normal file
@@ -0,0 +1,12 @@
|
||||
'use strict'
|
||||
|
||||
const {RECEIVE_DEVICES} = require('./action_constants')
|
||||
|
||||
module.exports = {
|
||||
receivedDevices: devices => {
|
||||
return {
|
||||
type: RECEIVE_DEVICES,
|
||||
devices,
|
||||
}
|
||||
}
|
||||
}
|
||||
20
lib/components/devices/proxy.js
Normal file
20
lib/components/devices/proxy.js
Normal file
@@ -0,0 +1,20 @@
|
||||
'use strict'
|
||||
|
||||
const {receivedDevices} = require('./actions')
|
||||
|
||||
module.exports = {
|
||||
// urls that should be called for the remote actions
|
||||
urls: {
|
||||
fetch: 'https://layla.amazon.de/api/devices/device',
|
||||
},
|
||||
// called after an remote http call to the device API has been successfully executed
|
||||
postHooks: {
|
||||
// called after the devices data has been fetched
|
||||
fetch: (store, data) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
store.dispatch(receivedDevices(data))
|
||||
resolve(data)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
13
lib/components/devices/reducer.js
Normal file
13
lib/components/devices/reducer.js
Normal file
@@ -0,0 +1,13 @@
|
||||
'use strict'
|
||||
const {RECEIVE_DEVICES} = require('./action_constants')
|
||||
|
||||
module.exports = (state = {}, action) => {
|
||||
switch (action.type) {
|
||||
case RECEIVE_DEVICES:
|
||||
return Object.assign({}, state, {
|
||||
devices: action.devices
|
||||
})
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
7
lib/components/devices/route.js
Normal file
7
lib/components/devices/route.js
Normal file
@@ -0,0 +1,7 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = {
|
||||
'/devices': (store, params, cb) => {
|
||||
cb(store.getState().devices.devices.devices)
|
||||
}
|
||||
}
|
||||
5
lib/components/household/action_constants.js
Normal file
5
lib/components/household/action_constants.js
Normal file
@@ -0,0 +1,5 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = {
|
||||
RECEIVE_HOUSEHOLD: 'RECEIVE_HOUSEHOLD',
|
||||
}
|
||||
12
lib/components/household/actions.js
Normal file
12
lib/components/household/actions.js
Normal file
@@ -0,0 +1,12 @@
|
||||
'use strict'
|
||||
|
||||
const {RECEIVE_HOUSEHOLD} = require('./action_constants')
|
||||
|
||||
module.exports = {
|
||||
receivedHousehold: household => {
|
||||
return {
|
||||
type: RECEIVE_HOUSEHOLD,
|
||||
household,
|
||||
}
|
||||
}
|
||||
}
|
||||
20
lib/components/household/proxy.js
Normal file
20
lib/components/household/proxy.js
Normal file
@@ -0,0 +1,20 @@
|
||||
'use strict'
|
||||
|
||||
const {receivedHousehold} = require('./actions')
|
||||
|
||||
module.exports = {
|
||||
// urls that should be called for the remote actions
|
||||
urls: {
|
||||
fetch: 'https://layla.amazon.de/api/household',
|
||||
},
|
||||
// called after an remote http call to the household API has been successfully executed
|
||||
postHooks: {
|
||||
// called after the household items have been fetched
|
||||
fetch: (store, data) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
store.dispatch(receivedHousehold(data))
|
||||
resolve(data)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
13
lib/components/household/reducer.js
Normal file
13
lib/components/household/reducer.js
Normal file
@@ -0,0 +1,13 @@
|
||||
'use strict'
|
||||
const {RECEIVE_HOUSEHOLD} = require('./action_constants')
|
||||
|
||||
module.exports = (state = {}, action) => {
|
||||
switch (action.type) {
|
||||
case RECEIVE_HOUSEHOLD:
|
||||
return Object.assign({}, state, {
|
||||
household: action.household
|
||||
})
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
7
lib/components/household/route.js
Normal file
7
lib/components/household/route.js
Normal file
@@ -0,0 +1,7 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = {
|
||||
'/household': (store, params, cb) => {
|
||||
res.send(store.getState().household.household)
|
||||
}
|
||||
}
|
||||
5
lib/components/network/action_constants.js
Normal file
5
lib/components/network/action_constants.js
Normal file
@@ -0,0 +1,5 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = {
|
||||
RECEIVE_NETWORK: 'RECEIVE_NETWORK',
|
||||
}
|
||||
12
lib/components/network/actions.js
Normal file
12
lib/components/network/actions.js
Normal file
@@ -0,0 +1,12 @@
|
||||
'use strict'
|
||||
|
||||
const {RECEIVE_NETWORK} = require('./action_constants')
|
||||
|
||||
module.exports = {
|
||||
receivedNetwork: network => {
|
||||
return {
|
||||
type: RECEIVE_NETWORK,
|
||||
network,
|
||||
}
|
||||
}
|
||||
}
|
||||
21
lib/components/network/proxy.js
Normal file
21
lib/components/network/proxy.js
Normal file
@@ -0,0 +1,21 @@
|
||||
'use strict'
|
||||
|
||||
const {receivedNetwork} = require('./actions')
|
||||
|
||||
module.exports = {
|
||||
// urls that should be called for the remote actions
|
||||
urls: {
|
||||
fetch: 'https://layla.amazon.de/api/phoenix',
|
||||
discover: 'https://layla.amazon.de/api/phoenix/discovery',
|
||||
},
|
||||
// called after an remote http call to the network API has been successfully executed
|
||||
postHooks: {
|
||||
// called after the network data has been fetched
|
||||
fetch: (store, data) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
store.dispatch(receivedNetwork(JSON.parse(data.networkDetail)))
|
||||
resolve(data)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
13
lib/components/network/reducer.js
Normal file
13
lib/components/network/reducer.js
Normal file
@@ -0,0 +1,13 @@
|
||||
'use strict'
|
||||
const {RECEIVE_NETWORK} = require('./action_constants')
|
||||
|
||||
module.exports = (state = {}, action) => {
|
||||
switch (action.type) {
|
||||
case RECEIVE_NETWORK:
|
||||
return Object.assign({}, state, {
|
||||
network: action.network
|
||||
})
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
7
lib/components/network/route.js
Normal file
7
lib/components/network/route.js
Normal file
@@ -0,0 +1,7 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = {
|
||||
'/network': (store, params, cb) => {
|
||||
res.send(store.getState().network.network)
|
||||
}
|
||||
}
|
||||
5
lib/components/notifications/action_constants.js
Normal file
5
lib/components/notifications/action_constants.js
Normal file
@@ -0,0 +1,5 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = {
|
||||
RECEIVE_NOTIFICATIONS: 'RECEIVE_NOTIFICATIONS',
|
||||
}
|
||||
12
lib/components/notifications/actions.js
Normal file
12
lib/components/notifications/actions.js
Normal file
@@ -0,0 +1,12 @@
|
||||
'use strict'
|
||||
|
||||
const {RECEIVE_NOTIFICATIONS} = require('./action_constants')
|
||||
|
||||
module.exports = {
|
||||
receivedNotifications: notifications => {
|
||||
return {
|
||||
type: RECEIVE_NOTIFICATIONS,
|
||||
notifications,
|
||||
}
|
||||
}
|
||||
}
|
||||
20
lib/components/notifications/proxy.js
Normal file
20
lib/components/notifications/proxy.js
Normal file
@@ -0,0 +1,20 @@
|
||||
'use strict'
|
||||
|
||||
const {receivedNotifications} = require('./actions')
|
||||
|
||||
module.exports = {
|
||||
// urls that should be called for the remote actions
|
||||
urls: {
|
||||
fetch: 'https://layla.amazon.de/api/notifications',
|
||||
},
|
||||
// called after an remote http call to the notifications API has been successfully executed
|
||||
postHooks: {
|
||||
// called after the notifications have been fetched
|
||||
fetch:(store, data) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
store.dispatch(receivedNotifications(data))
|
||||
resolve(data)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
13
lib/components/notifications/reducer.js
Normal file
13
lib/components/notifications/reducer.js
Normal file
@@ -0,0 +1,13 @@
|
||||
'use strict'
|
||||
const {RECEIVE_NOTIFICATIONS} = require('./action_constants')
|
||||
|
||||
module.exports = (state = {}, action) => {
|
||||
switch (action.type) {
|
||||
case RECEIVE_NOTIFICATIONS:
|
||||
return Object.assign({}, state, {
|
||||
notifications: action.notifications
|
||||
})
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
7
lib/components/notifications/route.js
Normal file
7
lib/components/notifications/route.js
Normal file
@@ -0,0 +1,7 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = {
|
||||
'/notifications': (store, params, cb) => {
|
||||
cb(store.getState().notifications.notifications.notifications)
|
||||
}
|
||||
}
|
||||
5
lib/components/shopping_list/action_constants.js
Normal file
5
lib/components/shopping_list/action_constants.js
Normal file
@@ -0,0 +1,5 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = {
|
||||
RECEIVE_SHOPPING_LIST: 'RECEIVE_SHOPPING_LIST',
|
||||
}
|
||||
12
lib/components/shopping_list/actions.js
Normal file
12
lib/components/shopping_list/actions.js
Normal file
@@ -0,0 +1,12 @@
|
||||
'use strict'
|
||||
|
||||
const {RECEIVE_SHOPPING_LIST} = require('./action_constants')
|
||||
|
||||
module.exports = {
|
||||
receivedShoppingList: todos => {
|
||||
return {
|
||||
type: RECEIVE_SHOPPING_LIST,
|
||||
items: todos,
|
||||
}
|
||||
}
|
||||
}
|
||||
20
lib/components/shopping_list/proxy.js
Normal file
20
lib/components/shopping_list/proxy.js
Normal file
@@ -0,0 +1,20 @@
|
||||
'use strict'
|
||||
|
||||
const {receivedShoppingList} = require('./actions')
|
||||
|
||||
module.exports = {
|
||||
// urls that should be called for the remote actions
|
||||
urls: {
|
||||
fetch: 'https://layla.amazon.de/api/todos?type=SHOPPING_ITEM&size=100',
|
||||
},
|
||||
// called after an remote http call to the shopping_list API has been successfully executed
|
||||
postHooks: {
|
||||
// called after the shopping items have been fetched
|
||||
fetch:(store, data) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
store.dispatch(receivedShoppingList(data.values))
|
||||
resolve(data)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
13
lib/components/shopping_list/reducer.js
Normal file
13
lib/components/shopping_list/reducer.js
Normal file
@@ -0,0 +1,13 @@
|
||||
'use strict'
|
||||
const {RECEIVE_SHOPPING_LIST} = require('./action_constants')
|
||||
|
||||
module.exports = (state = {}, action) => {
|
||||
switch (action.type) {
|
||||
case RECEIVE_SHOPPING_LIST:
|
||||
return Object.assign({}, state, {
|
||||
items: action.items
|
||||
})
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
7
lib/components/shopping_list/route.js
Normal file
7
lib/components/shopping_list/route.js
Normal file
@@ -0,0 +1,7 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = {
|
||||
'/shopping_list': (store, params, cb) => {
|
||||
cb(store.getState().shopping_list.items)
|
||||
}
|
||||
}
|
||||
5
lib/components/timeline/action_constants.js
Normal file
5
lib/components/timeline/action_constants.js
Normal file
@@ -0,0 +1,5 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = {
|
||||
RECEIVE_TIMELINE: 'RECEIVE_TIMELINE',
|
||||
}
|
||||
12
lib/components/timeline/actions.js
Normal file
12
lib/components/timeline/actions.js
Normal file
@@ -0,0 +1,12 @@
|
||||
'use strict'
|
||||
|
||||
const {RECEIVE_TIMELINE} = require('./action_constants')
|
||||
|
||||
module.exports = {
|
||||
receivedTimeline: timeline => {
|
||||
return {
|
||||
type: RECEIVE_TIMELINE,
|
||||
items: timeline,
|
||||
}
|
||||
}
|
||||
}
|
||||
19
lib/components/timeline/proxy.js
Normal file
19
lib/components/timeline/proxy.js
Normal file
@@ -0,0 +1,19 @@
|
||||
'use strict'
|
||||
|
||||
const {receivedTimeline} = require('./actions')
|
||||
|
||||
module.exports = {
|
||||
// urls that should be called for the remote actions
|
||||
urls: {
|
||||
fetch: 'https://layla.amazon.de/api/cards?limit=50',
|
||||
},
|
||||
postHooks: {
|
||||
// called after the timeline has been fetched
|
||||
fetch: (store, data) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
store.dispatch(receivedTimeline(data.cards))
|
||||
resolve(data)
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
13
lib/components/timeline/reducer.js
Normal file
13
lib/components/timeline/reducer.js
Normal file
@@ -0,0 +1,13 @@
|
||||
'use strict'
|
||||
const {RECEIVE_TIMELINE} = require('./action_constants')
|
||||
|
||||
module.exports = (state = {}, action) => {
|
||||
switch (action.type) {
|
||||
case RECEIVE_TIMELINE:
|
||||
return Object.assign({}, state, {
|
||||
items: action.items
|
||||
})
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
7
lib/components/timeline/route.js
Normal file
7
lib/components/timeline/route.js
Normal file
@@ -0,0 +1,7 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = {
|
||||
'/timeline': (store, params, cb) => {
|
||||
cb(store.getState().timeline.items)
|
||||
}
|
||||
}
|
||||
19
lib/components/todo/action_constants.js
Normal file
19
lib/components/todo/action_constants.js
Normal file
@@ -0,0 +1,19 @@
|
||||
'use strict'
|
||||
|
||||
const RECEIVE_TODOS = 'RECEIVE_TODOS'
|
||||
const FETCH_TODOS = 'FETCH_TODOS'
|
||||
const ADD_TODO = 'ADD_TODO'
|
||||
const ADDED_TODO = 'ADDED_TODO'
|
||||
const UPDATE_TODO = 'UPDATE_TODO'
|
||||
const DELETE_TODO = 'DELETE_TODO'
|
||||
const COMPLETE_TODO = 'COMPLETE_TODO'
|
||||
|
||||
module.exports = {
|
||||
RECEIVE_TODOS,
|
||||
FETCH_TODOS,
|
||||
ADD_TODO,
|
||||
ADDED_TODO,
|
||||
UPDATE_TODO,
|
||||
DELETE_TODO,
|
||||
COMPLETE_TODO,
|
||||
}
|
||||
61
lib/components/todo/actions.js
Normal file
61
lib/components/todo/actions.js
Normal file
@@ -0,0 +1,61 @@
|
||||
'use strict'
|
||||
|
||||
const {FETCH_TODOS, RECEIVED_TODOS, ADD_TODO, ADDED_TODO, UPDATE_TODO, DELETE_TODO, COMPLETE_TODO} = require('./action_constants')
|
||||
|
||||
const fetchTodos = todos => {
|
||||
return {
|
||||
type: FETCH_TODOS,
|
||||
items: todos,
|
||||
}
|
||||
}
|
||||
|
||||
const receivedTodos = todos => {
|
||||
return {
|
||||
type: RECEIVED_TODOS,
|
||||
items: todos,
|
||||
}
|
||||
}
|
||||
|
||||
const addTodo = todo => {
|
||||
return {
|
||||
type: ADD_TODO,
|
||||
item: todo,
|
||||
}
|
||||
}
|
||||
|
||||
const addedTodo = todo => {
|
||||
return {
|
||||
type: ADDED_TODO,
|
||||
item: todo,
|
||||
}
|
||||
}
|
||||
|
||||
const updateTodo = todo => {
|
||||
return {
|
||||
type: UPDATE_TODO,
|
||||
item: todo,
|
||||
}
|
||||
}
|
||||
|
||||
const deleteTodo = todo => {
|
||||
return {
|
||||
type: DELETE_TODO,
|
||||
item: todo,
|
||||
}
|
||||
}
|
||||
|
||||
const completeTodo = todo => {
|
||||
return {
|
||||
type: COMPLETE_TODO,
|
||||
item: todo,
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
receivedTodos,
|
||||
addTodo,
|
||||
addedTodo,
|
||||
updateTodo,
|
||||
deleteTodo,
|
||||
completeTodo,
|
||||
}
|
||||
61
lib/components/todo/proxy.js
Normal file
61
lib/components/todo/proxy.js
Normal file
@@ -0,0 +1,61 @@
|
||||
'use strict'
|
||||
|
||||
const {receivedTodos, addTodo, addedTodo} = require('./actions')
|
||||
|
||||
module.exports = {
|
||||
// urls that should be called for the remote actions
|
||||
urls: {
|
||||
fetch: 'https://layla.amazon.de/api/todos?type=TASK&size=100',
|
||||
add: 'https://layla.amazon.de/api/todos',
|
||||
complete: 'https://layla.amazon.de/api/todos/{ID}',
|
||||
delete: 'https://layla.amazon.de/api/todos/{ID}',
|
||||
update: 'https://layla.amazon.de/api/todos/{ID}',
|
||||
},
|
||||
// called before an remote http call to the todos API gets executed
|
||||
preHooks: {
|
||||
fetch: async options => [{method: 'GET', url: options.url}],
|
||||
// adds a new TODO
|
||||
add: async options => {
|
||||
let todoTemplate = {
|
||||
text: options.text,
|
||||
type: 'TASK',
|
||||
itemId: null,
|
||||
lastLocalUpdatedDate: null,
|
||||
lastUpdatedDate: null,
|
||||
createdDate: Date.now(),
|
||||
utteranceId: null,
|
||||
nbestItems: null,
|
||||
complete: false,
|
||||
version: null,
|
||||
deleted: false,
|
||||
reminderTime: null
|
||||
}
|
||||
// dispatch data about the item to be added
|
||||
store.dispatch(addTodo(todoTemplate))
|
||||
// build request options
|
||||
// TODO: Add error handling
|
||||
return [{method: 'POST', url: options.url, body: JSON.stringify(todoTemplate)}]
|
||||
}
|
||||
},
|
||||
// called after an remote http call to the todos API has been successfully executed
|
||||
postHooks: {
|
||||
// called after the todo list has been fetched
|
||||
fetch: async (store, raw_data) => {
|
||||
let data = []
|
||||
try {
|
||||
data = JSON.parse(raw_data)
|
||||
store.dispatch(receivedTodos(data.values))
|
||||
} catch (error) {
|
||||
// TODO: Add error handling
|
||||
}
|
||||
return data
|
||||
},
|
||||
// called after the new todo has been created
|
||||
add: async (store, raw_data) => {
|
||||
// TODO: Add error handling
|
||||
const data = JSON.parse(raw_data)
|
||||
store.dispatch(addedTodo(data))
|
||||
return data
|
||||
}
|
||||
}
|
||||
}
|
||||
42
lib/components/todo/reducer.js
Normal file
42
lib/components/todo/reducer.js
Normal file
@@ -0,0 +1,42 @@
|
||||
'use strict'
|
||||
const {RECEIVE_TODOS, RECEIVED_TODOS, ADD_TODO, ADDED_TODO} = require('./action_constants')
|
||||
const INITIAL_STATE = {toBeAdded: [], items: [], size: 0, lastUpdated: null, fetching: false, error: null}
|
||||
|
||||
// create an array of todos that are still queued, but not yet added
|
||||
const removeToBeAdded = (current, itemToBeRemoved) => {
|
||||
if (current.length === 1) return []
|
||||
return current.map(item => {
|
||||
if (item.text !== itemToBeRemoved.text) return item
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = (state = INITIAL_STATE, action) => {
|
||||
switch (action.type) {
|
||||
// received all todos
|
||||
case RECEIVED_TODOS:
|
||||
return Object.assign({}, state, {
|
||||
items: action.items,
|
||||
size: action.items.length,
|
||||
lastUpdated: Date.now(),
|
||||
})
|
||||
// trying to add a todo (request to amazon not yet made)
|
||||
case ADD_TODO:
|
||||
return Object.assign({}, state, {
|
||||
toBeAdded: state.toBeAdded.push(action.item),
|
||||
lastUpdated: Date.now(),
|
||||
})
|
||||
// todo has been added successfully
|
||||
case ADDED_TODO:
|
||||
// remove the item from the toBeadded array
|
||||
const toBeAdded = removeToBeAdded(state.toBeAdded, action.item)
|
||||
state.items.push(action.item)
|
||||
return Object.assign({}, state, {
|
||||
items: state.items,
|
||||
size: state.size + 1,
|
||||
toBeAdded: toBeAdded,
|
||||
lastUpdated: Date.now(),
|
||||
})
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
31
lib/components/todo/route.js
Normal file
31
lib/components/todo/route.js
Normal file
@@ -0,0 +1,31 @@
|
||||
'use strict'
|
||||
|
||||
const {receivedTodos, addTodo} = require('./actions')
|
||||
|
||||
module.exports = {
|
||||
// returns all todos
|
||||
'/todos': (store, params, cb) => cb(store.getState().todos),
|
||||
// adds a todo & returns all todos
|
||||
'/todos/add': (store, params, cb, Proxy) => Proxy.act('todo', 'add', params, result => {
|
||||
console.log('result', result)
|
||||
cb(result)
|
||||
}),
|
||||
// deletes a todo & returns all todos
|
||||
'/todos/delete': (store, params, cb, Proxy) => Proxy.act('todo', 'delete', params, async result => {
|
||||
const data = await Proxy.fetch('todo')
|
||||
store.dispatch(receivedTodos(data.todo.values))
|
||||
cb(data.todo.values)
|
||||
}),
|
||||
// completes a todo & returns all todos
|
||||
'/todos/complete': (store, params, cb, Proxy) => Proxy.act('todo', 'complete', params, async result => {
|
||||
const data = await Proxy.fetch('todo')
|
||||
store.dispatch(receivedTodos(data.todo.values))
|
||||
cb(data.todo.values)
|
||||
}),
|
||||
// updates a todo & returns all todos
|
||||
'/todos/update': (store, params, cb, Proxy) => Proxy.act('todo', 'update', params, async result => {
|
||||
const data = await Proxy.fetch('todo')
|
||||
store.dispatch(receivedTodos(data.todo.values))
|
||||
cb(data.todo.values)
|
||||
}),
|
||||
}
|
||||
5
lib/components/user_data/action_constants.js
Normal file
5
lib/components/user_data/action_constants.js
Normal file
@@ -0,0 +1,5 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = {
|
||||
RECEIVE_USER_DATA: 'RECEIVE_USER_DATA',
|
||||
}
|
||||
12
lib/components/user_data/actions.js
Normal file
12
lib/components/user_data/actions.js
Normal file
@@ -0,0 +1,12 @@
|
||||
'use strict'
|
||||
|
||||
const {RECEIVE_USER_DATA} = require('./action_constants')
|
||||
|
||||
module.exports = {
|
||||
receivedUserData: userData => {
|
||||
return {
|
||||
type: RECEIVE_USER_DATA,
|
||||
userData,
|
||||
}
|
||||
}
|
||||
}
|
||||
20
lib/components/user_data/proxy.js
Normal file
20
lib/components/user_data/proxy.js
Normal file
@@ -0,0 +1,20 @@
|
||||
'use strict'
|
||||
|
||||
const {receivedUserData} = require('./actions')
|
||||
|
||||
module.exports = {
|
||||
// urls that should be called for the remote actions
|
||||
urls: {
|
||||
fetch: 'https://layla.amazon.de/api/bootstrap?version=1.24.2445.0',
|
||||
},
|
||||
// called after an remote http call to the user data API has been successfully executed
|
||||
postHooks: {
|
||||
// called after the user data has been fetched
|
||||
fetch: (store, data) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
store.dispatch(receivedUserData(data))
|
||||
resolve(data)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
13
lib/components/user_data/reducer.js
Normal file
13
lib/components/user_data/reducer.js
Normal file
@@ -0,0 +1,13 @@
|
||||
'use strict'
|
||||
const {RECEIVE_USER_DATA} = require('./action_constants')
|
||||
|
||||
module.exports = (state = {}, action) => {
|
||||
switch (action.type) {
|
||||
case RECEIVE_USER_DATA:
|
||||
return Object.assign({}, state, {
|
||||
userData: action.userData
|
||||
})
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
7
lib/components/user_data/route.js
Normal file
7
lib/components/user_data/route.js
Normal file
@@ -0,0 +1,7 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = {
|
||||
'/user_data': (store, params, cb) => {
|
||||
cb(store.getState().user_data.userData)
|
||||
}
|
||||
}
|
||||
5
lib/components/wakewords/action_constants.js
Normal file
5
lib/components/wakewords/action_constants.js
Normal file
@@ -0,0 +1,5 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = {
|
||||
RECEIVE_WAKEWORDS: 'RECEIVE_WAKEWORDS',
|
||||
}
|
||||
12
lib/components/wakewords/actions.js
Normal file
12
lib/components/wakewords/actions.js
Normal file
@@ -0,0 +1,12 @@
|
||||
'use strict'
|
||||
|
||||
const {RECEIVE_WAKEWORDS} = require('./action_constants')
|
||||
|
||||
module.exports = {
|
||||
receivedWakewords: wakewords => {
|
||||
return {
|
||||
type: RECEIVE_WAKEWORDS,
|
||||
wakewords,
|
||||
}
|
||||
}
|
||||
}
|
||||
20
lib/components/wakewords/proxy.js
Normal file
20
lib/components/wakewords/proxy.js
Normal file
@@ -0,0 +1,20 @@
|
||||
'use strict'
|
||||
|
||||
const {receivedWakewords} = require('./actions')
|
||||
|
||||
module.exports = {
|
||||
// urls that should be called for the remote actions
|
||||
urls: {
|
||||
fetch: 'https://layla.amazon.de/api/wake-word',
|
||||
},
|
||||
// called after an remote http call to the user data API has been successfully executed
|
||||
postHooks: {
|
||||
// called after the user data has been fetched
|
||||
fetch: (store, data) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
store.dispatch(receivedWakewords(data.wakeWords))
|
||||
resolve(data)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
13
lib/components/wakewords/reducer.js
Normal file
13
lib/components/wakewords/reducer.js
Normal file
@@ -0,0 +1,13 @@
|
||||
'use strict'
|
||||
const {RECEIVE_WAKEWORDS} = require('./action_constants')
|
||||
|
||||
module.exports = (state = {}, action) => {
|
||||
switch (action.type) {
|
||||
case RECEIVE_WAKEWORDS:
|
||||
return Object.assign({}, state, {
|
||||
wakewords: action.wakewords
|
||||
})
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
7
lib/components/wakewords/route.js
Normal file
7
lib/components/wakewords/route.js
Normal file
@@ -0,0 +1,7 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = {
|
||||
'/wakewords': (store, params, cb) => {
|
||||
cb(store.getState().wakewords.wakewords)
|
||||
}
|
||||
}
|
||||
69
lib/cookies.js
Normal file
69
lib/cookies.js
Normal file
@@ -0,0 +1,69 @@
|
||||
'use strict'
|
||||
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const request =require('request')
|
||||
const Headers = require('./headers')
|
||||
|
||||
// files & endpoints
|
||||
const RAW_COOKIE_FILE = 'raw_jar.txt'
|
||||
const JSON_COOKIE_FILE = 'cookies.json'
|
||||
const TEST_ENDPOINT = 'https://layla.amazon.de/api/devices/device'
|
||||
|
||||
// Returns the full raw cookies path
|
||||
const getRawCookiePath = data_dir => path.join(data_dir, RAW_COOKIE_FILE)
|
||||
// Checks if the JSON cookie file exists
|
||||
const getJsonCookiePath = data_dir => path.join(data_dir, JSON_COOKIE_FILE)
|
||||
// Checks if the raw cookie file exists
|
||||
const checkRawCookiesExist = data_dir => fs.existsSync(getRawCookiePath(data_dir))
|
||||
// Checks if the JSON cookie file exists
|
||||
const checkJsonCookiesExist = data_dir => fs.existsSync(getJsonCookiePath(data_dir))
|
||||
// Checks if the raw cookie file exists
|
||||
const deleteRawCookies = data_dir => fs.removeSync(getRawCookiePath(data_dir))
|
||||
// Checks if the JSON cookie file exists
|
||||
const deleteJsonCookies = data_dir => fs.removeSync(getJsonCookiePath(data_dir))
|
||||
// Returns the cookies as a header compatible string
|
||||
const transformCookiesForHeader = json_cookies => {
|
||||
let string_cookies = ''
|
||||
json_cookies.forEach(cookie => {
|
||||
Object.keys(cookie).forEach(key => {
|
||||
if (key == 'name') return
|
||||
string_cookies += key == 'value' ? cookie.name + '=' + cookie.value : key + '=' + cookie[key]
|
||||
string_cookies += ';'
|
||||
})
|
||||
})
|
||||
return string_cookies
|
||||
}
|
||||
// Validate the cookie
|
||||
const validateCookie = (data_dir) => {
|
||||
const json_cookies = require(path.join(data_dir, JSON_COOKIE_FILE))
|
||||
const string_cookies = transformCookiesForHeader(json_cookies)
|
||||
const headers = Headers.getFetchHeaders(string_cookies)
|
||||
return new Promise((resolve, reject) => {
|
||||
request.get({headers, url: TEST_ENDPOINT}, (error, response, body) => {
|
||||
try {
|
||||
let contents = JSON.parse(body)
|
||||
if (contents.devices) {
|
||||
resolve([true, contents])
|
||||
} else {
|
||||
reject([false, null])
|
||||
}
|
||||
} catch (e) {
|
||||
reject([false, body])
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getRawCookiePath,
|
||||
getJsonCookiePath,
|
||||
checkRawCookiesExist,
|
||||
checkJsonCookiesExist,
|
||||
deleteRawCookies,
|
||||
deleteJsonCookies,
|
||||
validateCookie,
|
||||
transformCookiesForHeader,
|
||||
RAW_COOKIE_FILE,
|
||||
JSON_COOKIE_FILE,
|
||||
}
|
||||
28
lib/headers.js
Normal file
28
lib/headers.js
Normal file
@@ -0,0 +1,28 @@
|
||||
'use strict'
|
||||
|
||||
const ua = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/603.2.4 (KHTML, like Gecko) Version/10.1.1 Safari/603.2.4'
|
||||
|
||||
const base_headers = {
|
||||
'Pragma': 'no-cache',
|
||||
'Accept-Encoding': 'identity',
|
||||
'Accept-Language': 'de',
|
||||
'User-Agent': ua,
|
||||
'Accept': 'application/json, text/javascript, */*; q=0.01',
|
||||
'Cache-Control': 'no-cache',
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'Connection': 'keep-alive',
|
||||
'Referer': 'https://layla.amazon.de/spa/index.html',
|
||||
}
|
||||
|
||||
const getActionHeaders = (json_cookies, cookies) => {
|
||||
var csrf = '252916842'
|
||||
return Object.assign({}, getFetchHeaders(cookies + 'csrf=' + csrf + ';'), {csrf})
|
||||
}
|
||||
|
||||
const getFetchHeaders = (cookies) => Object.assign({}, base_headers, {'Cookie': cookies})
|
||||
|
||||
module.exports = {
|
||||
ua,
|
||||
getActionHeaders,
|
||||
getFetchHeaders,
|
||||
}
|
||||
218
lib/init.js
Normal file
218
lib/init.js
Normal file
@@ -0,0 +1,218 @@
|
||||
'use strict'
|
||||
|
||||
const os = require('os')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const homedir = require('homedir')
|
||||
const Store = require('./store')
|
||||
const phantomloader = require('./phantomloader')
|
||||
const webserver = require('./webserver')
|
||||
const Cookies = require('./cookies')
|
||||
const Headers = require('./headers')
|
||||
const Login = require('./login')
|
||||
const proxy = require('./proxy')
|
||||
|
||||
// file & directory names
|
||||
const DATA_DIR = '.alexis'
|
||||
const PHANTOMFILE = os.platform() === 'win32' ? 'phantomjs.exe' : 'phantomjs'
|
||||
|
||||
// computes the data directory
|
||||
const getDataDir = () => path.join(homedir(), DATA_DIR)
|
||||
// checks if the data directory exists
|
||||
const checkDataDirExists = () => fs.existsSync(getDataDir())
|
||||
// creates the data directory
|
||||
const createDataDir = () => fs.mkdirSync(getDataDir())
|
||||
// checks if the PhantomJS binary exists
|
||||
const checkPhantomExist = () => fs.existsSync(path.join(getDataDir(), PHANTOMFILE))
|
||||
// checks if the given options are valid
|
||||
const validateOptions = (options, verbose) => {
|
||||
let missing = [];
|
||||
['httpPort', 'email', 'pass'].forEach(option => {
|
||||
if (!options[option]) missing.push(option)
|
||||
})
|
||||
if (verbose && missing.length > 0) return missing.join(', ')
|
||||
if (missing.length > 0) return false
|
||||
return true
|
||||
}
|
||||
// validate options
|
||||
const optionsAreValid = (options = {}, invalidOptionsCb) => {
|
||||
if (!options.interval) options.interval = 10
|
||||
options.interval = options.interval * 1000
|
||||
if(!validateOptions(options)) {
|
||||
invalidOptionsCb()
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
// checks if data dir exists and creates it if not
|
||||
const doCreateUserdataDirectory = (data_dir, logger) => {
|
||||
if (!checkDataDirExists()) {
|
||||
logger.log('SETUP', `Creating data directory: ${data_dir}`)
|
||||
createDataDir()
|
||||
}
|
||||
}
|
||||
// checks if Phantom is installed, downloads it if not
|
||||
const mountPhantom = async (data_dir, logger) => {
|
||||
if (!checkPhantomExist()) {
|
||||
phantom_mounted = false
|
||||
const phantom_path_name = path.join(data_dir, PHANTOMFILE)
|
||||
logger.log('SETUP', 'Downloading Phantom')
|
||||
return await Phantom.download(phantom_path_name, data_dir)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Initializing flow
|
||||
const run = async (logger, options, invalidOptionsCb) => {
|
||||
// helper variable to determine if cookies exist
|
||||
let cookies_exist = false
|
||||
// helper variable to determine if cookies are valid
|
||||
let cookies_valid = false
|
||||
// helper variable to determine if login did work
|
||||
let login_success = false
|
||||
// init store
|
||||
let store = null
|
||||
// device data store
|
||||
let device_data = null
|
||||
// init PhantomJS downlaoder
|
||||
const Phantom = phantomloader(logger)
|
||||
// cache data dir location
|
||||
const data_dir = getDataDir()
|
||||
|
||||
// validate options
|
||||
if (!optionsAreValid(options, invalidOptionsCb)) return false
|
||||
|
||||
// set up redux store
|
||||
logger.log('SETUP', 'Creating in-memory store')
|
||||
store = Store()
|
||||
|
||||
// check if data dir exists, else create
|
||||
doCreateUserdataDirectory(data_dir, logger)
|
||||
|
||||
// checks if Phantom is installed, downloads it if not
|
||||
const phantom_mounted = await mountPhantom(data_dir, logger)
|
||||
|
||||
// check if phantom is mounted
|
||||
if (phantom_mounted !== true) {
|
||||
logger.log('ERROR', 'Error with mounting Phantom')
|
||||
return false
|
||||
}
|
||||
|
||||
// everythings okay
|
||||
logger.log('SETUP', 'Dependency check successful')
|
||||
|
||||
// cookie check
|
||||
if (Cookies.checkJsonCookiesExist(data_dir) && Cookies.checkJsonCookiesExist(data_dir)) {
|
||||
logger.log('SETUP', 'Cookies exist, trying to reuse them')
|
||||
// validate existing cookies
|
||||
try {
|
||||
cookies_valid = await Cookies.validateCookie(data_dir)
|
||||
device_data = cookies_valid[1]
|
||||
cookies_valid = cookies_valid[0]
|
||||
} catch (e) {
|
||||
cookies_valid = false
|
||||
}
|
||||
|
||||
// set flasg & log cookie state
|
||||
if (cookies_valid) {
|
||||
cookies_exist = true
|
||||
login_success = true
|
||||
logger.log('SETUP', 'Cookies valid, connection could be established')
|
||||
} else {
|
||||
cookies_exist = false
|
||||
logger.log('SETUP', 'Cookies invalid, connection could not be established')
|
||||
}
|
||||
}
|
||||
|
||||
// create a new set of cookies by logging in
|
||||
if (!cookies_exist) {
|
||||
logger.log('SETUP', 'No cookies found or invalid, logging in to create a fresh pair')
|
||||
try {
|
||||
login_success = await Login.fetchInitialCookies(
|
||||
Cookies.getJsonCookiePath(data_dir),
|
||||
Cookies.getRawCookiePath(data_dir),
|
||||
Headers.ua,
|
||||
options.email,
|
||||
options.pass,
|
||||
path.join(data_dir, PHANTOMFILE),
|
||||
)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
if (error === 'captcha') {
|
||||
logger.log('ERROR', 'Login failed, we got a captcha notice. Please change your public IP and try again.')
|
||||
} else {
|
||||
logger.log('ERROR', 'Login failed, please check your credentials')
|
||||
}
|
||||
login_success = false
|
||||
return false
|
||||
}
|
||||
// Login should´ve been successful, lets validate
|
||||
logger.log('SETUP', 'Login successful')
|
||||
|
||||
// cookie check
|
||||
if (Cookies.checkJsonCookiesExist(getDataDir()) && Cookies.checkJsonCookiesExist(getDataDir())) {
|
||||
// validate existing cookies
|
||||
try {
|
||||
cookies_valid = await Cookies.validateCookie(getDataDir())
|
||||
device_data = cookies_valid[1]
|
||||
cookies_valid = cookies_valid[0]
|
||||
} catch (e) {
|
||||
cookies_valid = false
|
||||
}
|
||||
|
||||
// set flasg & log cookie state
|
||||
if (cookies_valid) {
|
||||
logger.log('SETUP', 'Cookies valid, connection could be established')
|
||||
} else {
|
||||
logger.log('SETUP', 'Cookies invalid, connection could not be established')
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// set up proxy dialer
|
||||
const Proxy = await proxy(logger, data_dir, store)
|
||||
|
||||
// initial fetch of data
|
||||
logger.log('SETUP', 'Fetching initial data')
|
||||
const initial_data = await Proxy.fetchAll()
|
||||
logger.log('SETUP', 'Fetch finished')
|
||||
|
||||
// start webserver
|
||||
logger.log('SETUP', 'Starting webserver')
|
||||
try {
|
||||
await webserver(logger, options.httpPort, store, Proxy)
|
||||
} catch (error) {
|
||||
logger.log('ERROR', `Problem starting webserver: ${error}`)
|
||||
return false
|
||||
}
|
||||
|
||||
// set up scheduler to periodically fetch data
|
||||
logger.log('SETUP', `Setting up scheduler with interval: ${options.interval}ms`)
|
||||
|
||||
// display data about the echo devices we found
|
||||
logger.log('ECHO', `Found ${device_data.devices.length} Device(s) connected with this account`)
|
||||
device_data.devices.forEach(device => {
|
||||
logger.log('ECHO', `---------------------------------`)
|
||||
logger.log('ECHO', `Name: ${device.accountName}`)
|
||||
logger.log('ECHO', `Id: ${device.deviceAccountId}`)
|
||||
logger.log('ECHO', `Type: ${device.deviceFamily}`)
|
||||
logger.log('ECHO', `Active: ${device.online}`)
|
||||
logger.log('ECHO', `Mac Address: ${device.macAddress}`)
|
||||
})
|
||||
logger.log('ECHO', `---------------------------------`)
|
||||
|
||||
// setup done, system is ready to use
|
||||
logger.log('SETUP', 'Setup finished. Ready to use. Have Fun!')
|
||||
return true
|
||||
}
|
||||
|
||||
module.exports = (logger) => {
|
||||
return {
|
||||
getDataDir,
|
||||
checkDataDirExists,
|
||||
createDataDir,
|
||||
checkPhantomExist,
|
||||
run: run.bind(this, logger)
|
||||
}
|
||||
}
|
||||
48
lib/logger.js
Normal file
48
lib/logger.js
Normal file
@@ -0,0 +1,48 @@
|
||||
'use strict'
|
||||
|
||||
var chalk = require('chalk')
|
||||
|
||||
// colorize keywords
|
||||
var keywordMap = {
|
||||
SETUP: chalk.black.bgGreen.bold,
|
||||
PHANTOMJS: chalk.black.bgMagenta.bold,
|
||||
ROUTES: chalk.white.bgBlue.bold,
|
||||
WEBSERVER: chalk.black.bgCyan.bold,
|
||||
ERROR: chalk.black.bgRed.bold,
|
||||
ECHO: chalk.black.bgYellowBright.bold,
|
||||
}
|
||||
|
||||
// output formatted datetime before each log entry
|
||||
var formatConsoleDate = () => {
|
||||
var date = new Date()
|
||||
var hour = date.getHours()
|
||||
var minutes = date.getMinutes()
|
||||
var seconds = date.getSeconds()
|
||||
var milliseconds = date.getMilliseconds()
|
||||
return ((hour < 10) ? '0' + hour: hour) + ':' +
|
||||
((minutes < 10) ? '0' + minutes: minutes) + ':' +
|
||||
((seconds < 10) ? '0' + seconds: seconds) + '.' +
|
||||
('00' + milliseconds).slice(-3)
|
||||
}
|
||||
|
||||
// log with colour
|
||||
var logWithColour = function(item, text) {
|
||||
console.log(
|
||||
chalk.black.bgWhite(formatConsoleDate()),
|
||||
keywordMap[item]('[' + item + ']'),
|
||||
text)
|
||||
}
|
||||
|
||||
// log without colour
|
||||
var logWithoutColour = function(item, text) {
|
||||
console.log(formatConsoleDate(), '[' + item + ']', text)
|
||||
}
|
||||
|
||||
// export the log function
|
||||
module.exports = {
|
||||
setOption: function (use_colour, quiet) {
|
||||
if (use_colour === false) this.log = logWithoutColour
|
||||
if (quiet === true) this.log = function () {}
|
||||
},
|
||||
log: logWithColour
|
||||
}
|
||||
32
lib/login.js
Normal file
32
lib/login.js
Normal file
@@ -0,0 +1,32 @@
|
||||
'use strict'
|
||||
|
||||
const fs = require('fs')
|
||||
const Horseman = require('node-horseman')
|
||||
|
||||
const LOGIN_PAGE_URL = 'https://www.amazon.de/ap/signin?showRmrMe=1&openid.return_to=https%3A%2F%2Flayla.amazon.de&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.assoc_handle=amzn_dp_project_dee_de&openid.mode=checkid_setup&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&'
|
||||
|
||||
const fetchInitialCookies = (jsonCookiesFile, cookiesFile, useragent, user, password, phantomPath) => {
|
||||
const horseman = new Horseman({cookiesFile, phantomPath})
|
||||
return new Promise((resolve, reject) => {
|
||||
horseman
|
||||
.userAgent(useragent)
|
||||
.open(LOGIN_PAGE_URL)
|
||||
.text('#auth-fpp-link-bottom')
|
||||
.type('#ap_email', user)
|
||||
.type('#ap_password', password)
|
||||
.click('[name="rememberMe"]')
|
||||
.click('#signInSubmit')
|
||||
.waitForNextPage()
|
||||
.plainText()
|
||||
.then((text) => {
|
||||
if (text.search('Anmelden') !== -1) return reject(false)
|
||||
})
|
||||
.cookies()
|
||||
.then(cookies => resolve(fs.writeFileSync(jsonCookiesFile, JSON.stringify(cookies))))
|
||||
.close()
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
fetchInitialCookies,
|
||||
}
|
||||
177
lib/phantomloader.js
Normal file
177
lib/phantomloader.js
Normal file
@@ -0,0 +1,177 @@
|
||||
'use strict'
|
||||
|
||||
const os = require('os')
|
||||
const url = require('url')
|
||||
const path = require('path')
|
||||
const cp = require('child_process')
|
||||
const fs = require('fs-extra')
|
||||
const request = require('request')
|
||||
const extractZip = require('extract-zip')
|
||||
|
||||
const PHANTOM_VERSION = '2.1.1'
|
||||
const DEFAULT_CDN = 'https://github.com/Medium/phantomjs/releases/download/v2.1.1'
|
||||
const ARM_URL = 'https://github.com/fg2it/phantomjs-on-raspberry/raw/master/rpi-1-2-3/wheezy-jessie/v2.1.1/phantomjs'
|
||||
const PLATFORM = os.platform()
|
||||
const ARCH = os.arch()
|
||||
|
||||
const getDownloadUrl = () => {
|
||||
let downloadUrl = DEFAULT_CDN + '/phantomjs-' + PHANTOM_VERSION + '-'
|
||||
if (PLATFORM === 'linux' && ARCH === 'x64') {
|
||||
downloadUrl += 'linux-x86_64.tar.bz2'
|
||||
} else if (PLATFORM === 'linux' && ARCH == 'ia32') {
|
||||
downloadUrl += 'linux-i686.tar.bz2'
|
||||
} else if (PLATFORM === 'linux' && ARCH.search('arm') !== -1) {
|
||||
downloadUrl = ARM_URL
|
||||
} else if (PLATFORM === 'darwin') {
|
||||
downloadUrl += 'macosx.zip'
|
||||
} else if (PLATFORM === 'win32') {
|
||||
downloadUrl += 'windows.zip'
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
return downloadUrl
|
||||
}
|
||||
|
||||
const downloadPhantomjs = (logger, downloadUrl, data_dir) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const file_name = downloadUrl.split('/').pop()
|
||||
const downloaded_file = path.join(data_dir, file_name)
|
||||
// actually downloads the file
|
||||
const startDownload = (url, dest, cb) => {
|
||||
const file = fs.createWriteStream(dest)
|
||||
// download file
|
||||
request(url)
|
||||
.pipe(file)
|
||||
.on('error', cb)
|
||||
|
||||
file.on('close', () => {
|
||||
// close file & invoke callback
|
||||
file.close()
|
||||
cb()
|
||||
})
|
||||
}
|
||||
startDownload(downloadUrl, downloaded_file, err => {
|
||||
if (err) return reject(err)
|
||||
resolve(downloaded_file)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const extractDownload = (logger, filePath) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
var extractedPath = filePath + '-extract-' + Date.now()
|
||||
var options = {cwd: extractedPath}
|
||||
fs.mkdirsSync(extractedPath, '0777')
|
||||
// Make double sure we have 0777 permissions; some operating systems
|
||||
// default umask does not allow write by default.
|
||||
fs.chmodSync(extractedPath, '0777')
|
||||
if (filePath.substr(-4) === '.zip') {
|
||||
logger.log('PHANTOMJS','Extracting zip contents')
|
||||
extractZip(path.resolve(filePath), {dir: extractedPath}, err => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
resolve(extractedPath)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
logger.log('PHANTOMJS', 'Extracting tar contents (via spawned process)')
|
||||
cp.execFile('tar', ['jxf', path.resolve(filePath)], options, err => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
resolve(extractedPath)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const copyIntoPlace = (logger, extractedPath, archiveFile, targetPath) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
logger.log('PHANTOMJS', `Removing ${archiveFile}`)
|
||||
fs.removeSync(archiveFile)
|
||||
const folders = fs.readdirSync(extractedPath)
|
||||
const bin_dir = path.join(extractedPath, folders[0], 'bin')
|
||||
const files = fs.readdirSync(path.join(extractedPath, folders[0], 'bin'))
|
||||
let moved = false
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
let file = path.join(bin_dir, files[i])
|
||||
let filename = files[i]
|
||||
let target = targetPath + path.sep + filename
|
||||
logger.log('PHANTOMJS', `Copying extracted binary ${file} -> ${target}`)
|
||||
fs.moveSync(file, target)
|
||||
logger.log('PHANTOMJS', `Removing temporary download folder ${extractedPath}`)
|
||||
fs.removeSync(extractedPath)
|
||||
moved = target
|
||||
}
|
||||
|
||||
if (moved !== false) {
|
||||
resolve(moved)
|
||||
} else {
|
||||
reject()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const download = async (logger, phantom_bin, data_dir) => {
|
||||
const download_url = getDownloadUrl()
|
||||
let downloaded_file = ''
|
||||
let extracted_folder = ''
|
||||
let moved_file = ''
|
||||
// error out if we couldn't find a suitable platform/arch combination
|
||||
if (download_url === null) {
|
||||
logger.log('ERROR',
|
||||
`Unexpected platform or architecture: ${PLATFORM} / ${ARCH}\n` +
|
||||
'It seems there is no binary available for your platform/architecture\n' +
|
||||
`Try to install PhantomJS on your own & copy/link the executable to "${data_dir}"`)
|
||||
return false
|
||||
}
|
||||
logger.log('PHANTOMJS', `Downloading from "${download_url}" to "${data_dir}/"`)
|
||||
|
||||
// download phantomjs
|
||||
try {
|
||||
downloaded_file = await downloadPhantomjs(logger, download_url, data_dir)
|
||||
logger.log('PHANTOMJS', 'Download finished')
|
||||
} catch (e) {
|
||||
logger.log('ERROR', 'Error downloading PhantomJS - ' + e)
|
||||
return false
|
||||
}
|
||||
|
||||
// extract phantomjs (if needed)
|
||||
// if it´s ARM, we´re done now
|
||||
if (ARCH.search('arm') !== -1) return true
|
||||
logger.log('PHANTOMJS', `Extracting PhantomJS binary: ${downloaded_file}`)
|
||||
try {
|
||||
extracted_folder = await extractDownload(logger, downloaded_file)
|
||||
} catch (e) {
|
||||
logger.log('ERROR', 'Error extracting PhantomJS - ' + e)
|
||||
return false
|
||||
}
|
||||
|
||||
logger.log('PHANTOMJS', 'Successfully extracted archive: "${extracted_folder}"')
|
||||
|
||||
// move Phantom executable
|
||||
logger.log('PHANTOMJS', 'Moving executable to top level')
|
||||
try {
|
||||
moved_file = await copyIntoPlace(logger, extracted_folder, downloaded_file, data_dir)
|
||||
} catch (e) {
|
||||
logger.log('PHANTOMJS', 'Could not find extracted file')
|
||||
return false
|
||||
}
|
||||
|
||||
// make phantomjs executable
|
||||
try {
|
||||
fs.chmodSync(moved_file, '755')
|
||||
} catch (e) {
|
||||
logger.log('PHANTOMJS', 'Possible error with rights of downloaded Phantom binary')
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
module.exports = (logger) => {
|
||||
return {
|
||||
download: download.bind(this, logger)
|
||||
}
|
||||
}
|
||||
93
lib/proxy.js
Normal file
93
lib/proxy.js
Normal file
@@ -0,0 +1,93 @@
|
||||
'use strict'
|
||||
|
||||
const path = require('path')
|
||||
const klaw = require('klaw')
|
||||
const request = require('request')
|
||||
const through2 = require('through2')
|
||||
const Cookies = require('./cookies')
|
||||
const Headers = require('./headers')
|
||||
|
||||
// check if a resource is available
|
||||
const checkForResource = (fetchers, resource) => !fetchers[resource]
|
||||
// check if a ressources subressource has a registered preHook
|
||||
const checkForPostHooks = (fetchers, resource, subressource) => fetchers[resource].postHooks && fetchers[resource].postHooks[subressource]
|
||||
// check if a ressources subressource has a registered preHook
|
||||
const checkForPreHooks = (fetchers, resource, subressource) => fetchers[resource].preHooks && fetchers[resource].preHooks[subressource]
|
||||
// resource not found
|
||||
const resourceNotFound = (reject, resource) => reject(`Resource not found: ${resource}`)
|
||||
// helper stream transform to exclude all non proxy files from components
|
||||
const excludeNonComponentProxies = through2.obj(function (item, enc, next){
|
||||
if (path.basename(item.path) === path.basename(__filename) && item.path !== __filename) this.push(item)
|
||||
next()
|
||||
})
|
||||
// register all component proxies
|
||||
const registerProxies = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let items = {}
|
||||
klaw(__dirname)
|
||||
.pipe(excludeNonComponentProxies)
|
||||
.on('data', item => items[path.dirname(item.path).split(path.sep).pop()] = require(item.path))
|
||||
.on('error', reject)
|
||||
.on('end', resolve.bind(null, items))
|
||||
})
|
||||
}
|
||||
// get array of requests that should be made
|
||||
const getActions = async (fetchers, resource, subressource, params) => {
|
||||
if (checkForPreHooks(fetchers, resource, subressource)) {
|
||||
return await fetchers[resource].preHooks[subressource](Object.assign({}, params, {url: fetchers[resource].urls[subressource]}))
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
// called when the resquest is done
|
||||
const requestDone = async (resolve, reject, fetchers, store, resource, subressource, err, response, body) => {
|
||||
if (err) return reject(err)
|
||||
try {
|
||||
if (checkForPostHooks(fetchers, resource, subressource)) body = await fetchers[resource].postHooks[subressource](store, body)
|
||||
resolve({[resource]: body})
|
||||
} catch (error) {
|
||||
reject(error)
|
||||
}
|
||||
}
|
||||
// fetch one ressource
|
||||
const fetch = async (logger, headers, fetchers, store, resource, subressource = 'fetch', params = {}) => {
|
||||
if (checkForResource(fetchers, resource)) return resourceNotFound(reject, resource)
|
||||
const actions = await getActions(fetchers, resource, subressource)
|
||||
const requests = actions.map(action => new Promise((resolve, reject) => request(Object.assign({}, {headers}, action), requestDone.bind(null, resolve, reject, fetchers, store, resource, subressource))))
|
||||
const data = await Promise.all(requests)
|
||||
return data
|
||||
}
|
||||
|
||||
// fetch all registered resources
|
||||
const fetchAll = (logger, headers, fetchers, store) => Promise.all(Object.keys(fetchers).map(resource => fetch(logger, headers, fetchers, store, resource)))
|
||||
|
||||
// fetch resources periodically
|
||||
const fetchInterval = (logger, headers, fetchers, store, interval = 10000) => setInterval(() => resolveInterval(fetchAll(logger, headers, fetchers)), interval)
|
||||
|
||||
// fire actions
|
||||
const act = async (logger, headers, fetchers, store, resource, subresource, params, cb) => {
|
||||
if (checkForResource(fetchers, resource) || !checkForPreHooks(fetchers, resource, subresource)) return resourceNotFound(reject, resource)
|
||||
const actions = await fetchers[resource].preHooks[subresource](Object.assign({}, params, {url: fetchers[resource].urls[subresource]}))
|
||||
const response = () => actions.map(action => {
|
||||
return new Promise((resolve, reject) => request(Object.assign({}, {headers}, action), requestDone.bind(null, resolve, reject, fetchers, store, resource, subresource)))
|
||||
})
|
||||
const res = await Promise.all(response())
|
||||
console.log('res', res)
|
||||
cb(res)
|
||||
}
|
||||
|
||||
module.exports = async (logger, data_dir, store, interval) => {
|
||||
const json_cookies = require(path.join(data_dir, Cookies.JSON_COOKIE_FILE))
|
||||
const cookies = Cookies.transformCookiesForHeader(json_cookies)
|
||||
const headers = Headers.getFetchHeaders(cookies)
|
||||
const action_headers = Headers.getActionHeaders(json_cookies, cookies)
|
||||
const fetchers = await registerProxies()
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve({
|
||||
act: act.bind(this, logger, action_headers, fetchers, store),
|
||||
fetch: fetch.bind(this, logger, headers, fetchers, store),
|
||||
fetchAll: fetchAll.bind(this, logger, headers, fetchers, store),
|
||||
fetchInterval: fetchInterval.bind(this, logger, headers, fetchers, store, interval),
|
||||
})
|
||||
})
|
||||
}
|
||||
28
lib/reducer.js
Normal file
28
lib/reducer.js
Normal file
@@ -0,0 +1,28 @@
|
||||
'use strict'
|
||||
|
||||
const {combineReducers} = require('redux')
|
||||
|
||||
// load sub reducers from components
|
||||
const todos = require('./components/todo/reducer')
|
||||
const shopping_list = require('./components/shopping_list/reducer')
|
||||
const timeline = require('./components/timeline/reducer')
|
||||
const network = require('./components/network/reducer')
|
||||
const devices = require('./components/devices/reducer')
|
||||
const household = require('./components/household/reducer')
|
||||
const notifications = require('./components/notifications/reducer')
|
||||
const user_data = require('./components/user_data/reducer')
|
||||
const wakewords = require('./components/wakewords/reducer')
|
||||
|
||||
module.exports = () => {
|
||||
return combineReducers({
|
||||
todos,
|
||||
shopping_list,
|
||||
timeline,
|
||||
network,
|
||||
devices,
|
||||
household,
|
||||
notifications,
|
||||
user_data,
|
||||
wakewords,
|
||||
})
|
||||
}
|
||||
12
lib/store.js
Normal file
12
lib/store.js
Normal file
@@ -0,0 +1,12 @@
|
||||
'use strict'
|
||||
|
||||
const {createStore, applyMiddleware} = require('redux')
|
||||
const rootReducer = require('./reducer')
|
||||
|
||||
// local shadowed store
|
||||
let store = null
|
||||
|
||||
module.exports = () => {
|
||||
if (store === null) store = createStore(rootReducer(), {})
|
||||
return store
|
||||
}
|
||||
43
lib/telnetserver.js
Normal file
43
lib/telnetserver.js
Normal file
@@ -0,0 +1,43 @@
|
||||
'use strict'
|
||||
|
||||
const net = require('net')
|
||||
|
||||
let server = null
|
||||
let sockets = []
|
||||
|
||||
// cleans the input of carriage return, newline
|
||||
const cleanInput = data => data.toString().replace(/(\r\n|\n|\r)/gm, '')
|
||||
|
||||
// executed when data is received from a socket
|
||||
const receiveData = (socket, data) => {
|
||||
const cleanData = cleanInput(data)
|
||||
if (cleanData === "@quit") {
|
||||
socket.end('Goodbye!\n')
|
||||
} else {
|
||||
for (let i = 0; i < sockets.length; i++) {
|
||||
if (sockets[i] !== socket) {
|
||||
sockets[i].write(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// executed when a socket ends
|
||||
const closeSocket = socket => {
|
||||
const i = sockets.indexOf(socket)
|
||||
if (i != -1) sockets.splice(i, 1)
|
||||
}
|
||||
|
||||
// callback method executed when a new TCP socket is opened
|
||||
const newSocket = socket => {
|
||||
sockets.push(socket)
|
||||
socket.write('Welcome to the Telnet server!\n')
|
||||
socket.on('data', data => receiveData(socket, data))
|
||||
socket.on('end', () => closeSocket(socket))
|
||||
}
|
||||
|
||||
// create a new server
|
||||
module.exports = (port) => {
|
||||
if (server === null) server = net.createServer(newSocket).listen(port)
|
||||
return server
|
||||
}
|
||||
55
lib/webserver.js
Normal file
55
lib/webserver.js
Normal file
@@ -0,0 +1,55 @@
|
||||
'use strict'
|
||||
|
||||
const express = require('express')
|
||||
const app = express()
|
||||
|
||||
// routes
|
||||
const all = require('./components/all/route')
|
||||
const todos = require('./components/todo/route')
|
||||
const shopping_list = require('./components/shopping_list/route')
|
||||
const timeline = require('./components/timeline/route')
|
||||
const network = require('./components/network/route')
|
||||
const devices = require('./components/devices/route')
|
||||
const household = require('./components/household/route')
|
||||
const notifications = require('./components/notifications/route')
|
||||
const user_data = require('./components/user_data/route')
|
||||
const wakewords = require('./components/wakewords/route')
|
||||
|
||||
module.exports = (logger, port, store, Proxy) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
// collect routes
|
||||
const components = [
|
||||
all,
|
||||
todos,
|
||||
shopping_list,
|
||||
timeline,
|
||||
network,
|
||||
devices,
|
||||
household,
|
||||
notifications,
|
||||
user_data,
|
||||
wakewords,
|
||||
]
|
||||
|
||||
// register routes
|
||||
components.forEach(component => {
|
||||
Object.keys(component).forEach(route => {
|
||||
logger.log('ROUTES', `(webserver) Register route ${route}`)
|
||||
app.get(route, (req, res) => {
|
||||
component[route](store, req.query, (data) => {
|
||||
res.send(data)
|
||||
}, Proxy)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// listen on errors
|
||||
app.on('error', reject)
|
||||
|
||||
// start webserver
|
||||
app.listen(port, () => {
|
||||
logger.log('WEBSERVER', `Webserver started: http://localhost:${port}/`)
|
||||
resolve(true)
|
||||
})
|
||||
})
|
||||
}
|
||||
1378
package-lock.json
generated
Normal file
1378
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
26
package.json
Normal file
26
package.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "alexis",
|
||||
"version": "1.0.0",
|
||||
"description": "Local proxy server to remote control your Amazon Echo devices",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "Sebastian Golasch <public@asciidisco.com> (http://asciidisco.com/)",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chalk": "2.1.0",
|
||||
"express": "4.15.4",
|
||||
"extract-zip": "1.6.5",
|
||||
"faye-websocket": "0.11.1",
|
||||
"fs-extra": "4.0.1",
|
||||
"homedir": "0.6.0",
|
||||
"klaw": "2.1.0",
|
||||
"meow": "3.7.0",
|
||||
"node-horseman": "3.3.0",
|
||||
"redux": "3.7.2",
|
||||
"request": "2.81.0",
|
||||
"through2": "2.0.3",
|
||||
"websocket": "1.0.24"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user