bravia-remote/netflixkb.js

171 lines
5.2 KiB
JavaScript

// 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)
}