initial commit
commit
bad01f6ef4
@ -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
|
@ -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)
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
@ -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})
|
Loading…
Reference in New Issue