171 lines
5.2 KiB
JavaScript
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)
|
|
}
|