initial commit

master
Yorick van Pelt 2017-01-08 22:24:52 +01:00
commit bad01f6ef4
6 changed files with 261 additions and 0 deletions

59
bravia.js Normal file
View File

@ -0,0 +1,59 @@
const rp = require('request-promise')
const Promise = require('bluebird')
const utils = require('./utils')
class Bravia {
constructor(ip, secret) {
Object.assign(this, {ip, secret, cmd_cache: {}})
}
request(options) {
options.url = `http://${this.ip}${options.path}`
options.headers = options.headers || {}
options.headers['X-Auth-PSK'] = this.secret
return rp.post(options)
}
sendIRCC(ircc) {
const body = `<?xml version="1.0"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <s:Body>
<u:X_SendIRCC xmlns:u="urn:schemas-sony-com:service:IRCC:1">
<IRCCCode>${ircc}</IRCCCode>
</u:X_SendIRCC>
</s:Body></s:Envelope>`
return this.request({
path: '/sony/IRCC', body, headers: {
'Content-Type': 'text/xml charset=UTF-8',
'SOAPACTION': '"urn:schemas-sony-com:service:IRCC:1#X_SendIRCC"'
}
})
}
sendRPC(endpoint, method, ...params) {
// endpoint \in {system, accessControl, encryption, recording, browser, appControl}
// "getMethodTypes", "" is a good one to try
return this.request({
path: `/sony/${endpoint}`,
json: true,
body: { method, params, id: 1, version: "1.0" }
})
}
lookupCommand(cmd, do_lookup = true) {
if (this.cmd_cache[cmd]) return Promise.resolve(this.cmd_cache[cmd])
if(do_lookup) return this.sendRPC("system", "getRemoteControllerInfo").then((body) => {
body.result[1].forEach(({name, value}) => this.cmd_cache[name] = value)
return this.lookupCommand(cmd, false)
})
else return Promise.reject("command not found")
}
sendCommand(cmd) {
console.log("sending", cmd)
return typeof cmd === 'number' ? Promise.delay(cmd) :
Array.isArray(cmd) ? this.sequence(cmd) :
this.lookupCommand(cmd).then(ircc => this.sendIRCC(ircc))
}
sequence([cmd, ...commands]) {
if (cmd == undefined) return Promise.resolve()
return this.sendCommand(cmd).then(() => this.sequence(commands))
}
delaySequence(cmds, delay=250) {
this.sequence(utils.alternate(cmds, 250))
}
}
module.exports = Bravia

1
cmds.json Normal file

File diff suppressed because one or more lines are too long

170
netflixkb.js Normal file
View File

@ -0,0 +1,170 @@
// Q: why would you do this
// A: the password on the netflix account I use is long
// and it doesn't have proper text input support
// Q: how do I use this
// A: bravia.delaySequence(require('netflixkb.js').NetflixPW.sequence("yourpassword"))
"use strict"
const FibonacciHeap = require('@tyriar/fibonacci-heap')
// priority-queue dijkstra, reference https://en.wikipedia.org/wiki/Dijkstra's_algorithm
function find_path(target, prev) {
const S = []
let u = {node: target}
while(prev.has(u.node)) {
u = prev.get(u.node)
S.push(u)
}
return [S.reverse().map(x => x.label), S[S.length - 1].node]
}
function dijkstra(source, target) {
const dist = new Map()
const prev = new Map()
const heapnodes = new Map()
const Q = new FibonacciHeap()
dist.set(source, 0)
heapnodes.set(source, Q.insert(0, source))
while(!Q.isEmpty()) {
const {value: u} = Q.extractMinimum()
heapnodes.delete(u)
if (u == target) {
return find_path(target, prev)
}
if (u.transitions) {
for(const [cost, v, label] of u.transitions()) {
const alt = dist.get(u) + cost
if (alt < (dist.has(v) ? dist.get(v) : Infinity)) {
dist.set(v, alt)
prev.set(v, Object.freeze({node: u, label}))
if (heapnodes.has(v)) Q.decreaseKey(heapnodes.get(v), alt)
else heapnodes.set(v, Q.insert(alt, v))
}
}
}
}
return undefined
}
// TODO: find better solution for the special keys
// wraps to left and right
// \x1: shift. \x2: symbols, \x11: backspace
// 3: diacritics \x05: uppercase symbols, \x06: uppercase diacritics
// \x10: too lazy, some sort of superscript a
const netflixpw_kb = Object.freeze([[
"1234567890",
"qwertyuiop",
"asdfghjkl-",
"\x01\x01zxcvbnm'",
"\x02\x02\x03\x03 \x11\x11\x11"
],[
"1234567890",
"QWERTYUIOP",
"ASDFGHJKL-",
"\x00\x00ZXCVBNM'",
"\x05\x05\x06\x06 \x11\x11\x11"
],[
"`~!@#$%^&*",
"()-_=+[]{}",
"\\|;:'\",.<>",
"/?¡¿\x10°¢€£¥",
"\x00\x00\x03\x03 \x11\x11\x11"
]
])
// 0: standard. 1: shift. 2: special chars. 3: uppercase special chars
// 11: backspace. 12: @gmail.com. 13: @hotmail.com. 14: @live.nl. 15: .com.
// 16: .ca 17 .net 18 .edu 19 .org 1a .gov
const netflixemail = Object.freeze([[
"1234567890",
"qwertyuiop",
"asdfghjkl-",
"\x01\x01zxcvbnm_",
"\x12\x12\x12\x13\x13\x13\x13\x14\x14\x14",
"\x02\x02@@.\x15\x15\x11\x11\x11",
],[
"1234567890",
"QWERTYUIOP",
"ASDFGHJKL-",
"\x00\x00ZXCVBNM_",
"\x12\x12\x12\x13\x13\x13\x13\x14\x14\x14",
"\x03\x03@@.\x15\x15\x11\x11\x11",
],[
"1234567890",
"`~!@#$%^&*",
"+-_{}|'./?",
"\x16\x16\x17\x17\x18\x18\x19\x19\x1a\x1a",
"\x12\x12\x12\x13\x13\x13\x13\x14\x14\x14",
"\x00\x00@@.\x15\x15\x11\x11\x11",
]
])
const netflixsearch = Object.freeze([[
' \x11\x11\x11',
'abcdef',
'ghijkl',
'mnopqr',
'stuvwx',
'yz1234',
'567890'
]])
class Keyboard {
constructor(chars, startpos, wraparound = true, centeroffs = true) {
Object.assign(this, {
chars, depth: chars.length, height: chars[0].length, width: chars[0][0].length,
startpos, wraparound, centeroffs // workaround because the search keyboard is different
})
this.nodes = chars.map((kb, kbid) =>
kb.map((row, rowid) =>
row.split('').map((letter, colid) =>
this.mkNode(kbid, rowid, colid))))
}
findGroupInfo(kb, row, col) {
const kbrow = this.chars[kb][row]
const idxs = kbrow.split('').map((ltr,id) => ltr == kbrow[col] ? id : -1).filter(id => id>=0)
return Object.freeze({left: idxs[0], size: idxs.length})
}
findGroupCenter(kb, row, col) {
col = (col + this.width) % this.width // fix wraparound
const {left, size} = this.findGroupInfo(kb, row, col)
if (!this.centeroffs) return this.nodes[kb][row][left]
return this.nodes[kb][row][left + Math.ceil(size / 2) - 1]
}
mkNode(kb, row, col) {
return Object.freeze({
letter: this.chars[kb][row][col], kb, row, col,
transitions: () => {
const {left, size} = this.findGroupInfo(kb, row, col)
const transitions = {
'Left': this.findGroupCenter(kb, row, left - 1),
'Right': this.findGroupCenter(kb, row, left + size)
}
if (!this.wraparound && col == 0) delete transitions['Left']
if (!this.wraparound && col == this.width-1) delete transitions['Right']
if (row != 0) transitions['Up'] = this.nodes[kb][row-1][col]
if (row != this.height-1) transitions['Down'] = this.nodes[kb][row+1][col]
{
const char = this.chars[kb][row][col]
const cc = char.charCodeAt(0)
if (cc < this.depth) transitions['DpadCenter'] = this.findGroupCenter(cc, row, col)
else if (cc > 15) transitions['DpadCenter'] = char
}
return Object.keys(transitions).map(tname => [1, transitions[tname], tname])
}
})
}
lookupNode(z, y, x) { return this.nodes[z][y][x] }
sequence(string, goback = false, start = undefined) {
if (!start) start = this.lookupNode(...this.startpos)
let pos = start
const seq = []
for(const char of string) {
let s2;
[s2, pos] = dijkstra(pos, char)
seq.push(s2)
}
if (goback) return [[].concat(...seq, dijkstra(pos, start)[0]), start]
return [[].concat(...seq), pos]
}
}
module.exports = {
Keyboard,
NetflixPW: new Keyboard(netflixpw_kb, [0,2,4]),
NetflixMail: new Keyboard(netflixemail, [0,2,4]),
NetflixSearch: new Keyboard(netflixsearch, [0, 1, 0], false, false)
}

15
package.json Normal file
View File

@ -0,0 +1,15 @@
{
"name": "bravia-remote",
"version": "1.0.0",
"description": "",
"main": "bravia.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"@tyriar/fibonacci-heap": "^1.0.10",
"request-promise": "^4.1.1"
}
}

9
tasks.js Normal file
View File

@ -0,0 +1,9 @@
const {repeat} = require('./utils')
const speakermenu = [
'WakeUp', 2000, 'Exit', 300, 'ActionMenu', 1000, repeat(['Up', 250], 9), repeat(['Down', 250], 7), 'DpadCenter', 1000
]
const speakermode = [speakermenu, 'Down', 500, 'DpadCenter', 500]
const unspeakermode = [speakermenu, 'Up', 500, 'DpadCenter', 500]
const hdmi2audio = ['WakeUp', 5000, 'Hdmi2', 1000, speakermode, 'PicOff']
Object.assign(module.exports, {speakermenu, speakermode, unspeakermode, hdmi2audio})

7
utils.js Normal file
View File

@ -0,0 +1,7 @@
function *repeat(seq, times) {
for(let i = 0; i < times; i++) yield *seq
}
function *alternate(a, delim) {
for(const x of a) { yield *[x, delim] }
}
Object.assign(module.exports, {repeat, alternate})