Last active
June 6, 2019 10:56
-
-
Save sentient06/f542615b66304ad36c738997aa5b3664 to your computer and use it in GitHub Desktop.
Roku remote
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# To run this code, use the IP address of the roku device as such: | |
# ruby ./RokuRemote.rb 192.168.99.99 | |
require 'io/console' | |
require 'uri' | |
require 'net/http' | |
unless ARGV[0] | |
puts "Need IP of device" | |
exit 0 | |
else | |
@device_ip = ARGV[0] | |
end | |
@exit_msg = "⌽" | |
# Sends a POST request to the roku device. | |
def send_http_request_with_code code | |
begin | |
uri = URI.parse("http://#{@device_ip}:8060/keypress/#{code}") | |
request = Net::HTTP.new(uri.host, uri.port) | |
request.read_timeout = 1 # seconds | |
req = Net::HTTP::Post.new(uri.path) | |
res = request.request(req) | |
rescue StandardError | |
print "E" | |
end | |
end | |
# Reads keypresses from the user including 2 and 3 escape character sequences. | |
def read_char | |
STDIN.echo = false | |
STDIN.raw! | |
input = STDIN.getc.chr | |
if input == "\e" then | |
input << STDIN.read_nonblock(3) rescue nil | |
input << STDIN.read_nonblock(2) rescue nil | |
end | |
ensure | |
STDIN.echo = true | |
STDIN.cooked! | |
return input | |
end | |
# Keys recognised by the roku API: | |
@known_keys = [ | |
"Home", | |
"Rev", | |
"Fwd", | |
"Play", | |
"Select", | |
"Left", | |
"Right", | |
"Down", | |
"Up", | |
"Back", | |
"InstantReplay", | |
"Info", | |
"Backspace", | |
"Search", | |
"Enter" | |
] | |
# User-friendly output: | |
@ui_keys = { | |
"Home" => "H", | |
"Rev" => "↞", | |
"Fwd" => "↠", | |
"Play" => "P", | |
"Select" => "✓", | |
"Left" => "←", | |
"Right" => "→", | |
"Down" => "↓", | |
"Up" => "↑", | |
"Back" => "⬅", | |
"InstantReplay" => "↻", | |
"Info" => "ℹ", | |
"Backspace" => "⌫", | |
"Search" => "⌕", | |
"Enter" => "⏎", | |
"?" => "?", | |
"1" => "⚡", | |
"2" => "⚡", | |
"3" => "⚡", | |
"4" => "⚡", | |
"5" => "⚡", | |
"6" => "⚡", | |
"d" => "⚡", | |
"j" => "⚡" | |
} | |
# Character-based aliases: | |
@characters = { | |
"h" => "Home", | |
"i" => "Info", | |
"y" => "InstantReplay", | |
"s" => "Search", | |
"," => "Rev", | |
"<" => "Rev", | |
"." => "Fwd", | |
">" => "Fwd", | |
"p" => "Play" | |
} | |
# Sequences of keys: | |
@konami = { | |
"1" => ["Home", "Home", "Home", "Home", "Home", "Up", "Rev", "Rev", "Fwd", "Fwd"], | |
"2" => ["Home", "Home", "Home", "Home", "Home", "Fwd", "Fwd", "Fwd", "Rev", "Rev"], | |
"3" => ["Home", "Home", "Home", "Home", "Home", "Up", "Right", "Down", "Left", "Up"], | |
"4" => ["Home", "Home", "Home", "Down", "Down", "Right", "Left", "Down", "Left", "Left"], | |
"5" => ["Home", "Home", "Home", "Up", "Up", "Right", "Left", "Right", "Left", "Right"], | |
"6" => ["Home", "Home", "Home", "Up", "Up", "Left", "Right", "Left", "Right", "Left"], | |
"d" => ["Play", "Play", "Play"], | |
"j" => ["Play", "Play", "Play", "Up", "Down", "Select", "Up", "Select"], | |
} | |
# History to show on screen: | |
@keys_history = [] | |
@max_chars = 88 | |
@sleeping_time = 3 | |
# Visibility of the history: | |
@limit = (@max_chars - 2) / 2 | |
def print_empty | |
print "\r" | |
print " " * @max_chars | |
end | |
def print_dash | |
print "\r" | |
print "-" * @max_chars | |
end | |
def print_sequence | |
print "\r " | |
@keys_history.last(@limit).each {|x| print @ui_keys[x], " " } | |
end | |
# Print prompt for repeat value: | |
def print_repeat_value | |
if @sequence_number == 0 | |
seq = "" | |
else | |
seq = @sequence_number | |
end | |
print_empty | |
print "\r Repeat: #{seq}" | |
end | |
# Action that triggers the request based on the key pressed: | |
def key_down(key) | |
if @repeat_sequence == true | |
return | |
end | |
if @known_keys.include? key | |
send_http_request_with_code key | |
@keys_history.push key | |
else | |
@keys_history.push "?" | |
end | |
print_sequence | |
end | |
# Method that enters a sequence of keys: | |
def punch_sequence sequence | |
sequence.each do |x| | |
key_down x | |
sleep @sleeping_time / 10.0 | |
end | |
end | |
@repeat_sequence = false | |
@sequence_number = 0 | |
# Method to grab the key based on its value rather than its code: | |
def char_down character | |
if @characters.include? character | |
key_down @characters[character] | |
elsif @konami.include? character | |
@keys_history.push character | |
punch_sequence @konami[character] | |
elsif character == "k" | |
@keys_history = [] | |
@sleeping_time = 3 | |
print_empty | |
print_sequence | |
elsif character == "q" | |
puts @exit_msg | |
exit 0 | |
elsif character == "]" | |
unless @sleeping_time - 1 < 2 | |
@sleeping_time -= 1 | |
end | |
elsif character == "[" | |
@sleeping_time += 1 | |
elsif character == "r" | |
@repeat_sequence = true | |
print "\n" | |
print_repeat_value | |
else | |
key_down "?" | |
end | |
end | |
# Repeat sequence number as text: | |
def compute_number character | |
number_text = @sequence_number.to_s | |
number_text += character | |
@sequence_number = number_text.to_i | |
end | |
# Return behaves differently when a sequence number is being entered: | |
def handle_return | |
if @repeat_sequence == true | |
@repeat_sequence = false | |
print "\033[F\n" | |
print_empty | |
print "\033[F" | |
print_sequence | |
if @sequence_number <= @keys_history.length | |
repeat_this = @keys_history.last(@sequence_number) | |
punch_sequence repeat_this | |
end | |
@sequence_number = 0 | |
else | |
key_down "Select" | |
end | |
end | |
# Backspace behaves differently when a sequence number is being entered: | |
def handle_backspace | |
if @repeat_sequence == true | |
number_text = @sequence_number.to_s | |
number_text.chomp!(number_text[-1]) | |
@sequence_number = number_text.to_i | |
print_repeat_value | |
else | |
key_down "Back" | |
end | |
end | |
# Method to read the key press event and act accordingly: | |
def show_single_key | |
c = read_char | |
case c | |
when " " # Space | |
key_down "Select" | |
when "\t" # Tab | |
key_down "TAB" | |
when "\r" # Return | |
handle_return | |
when "\n" # Line feed? | |
key_down "LINE FEED" | |
when "\e" # Escape | |
key_down "Back" | |
when "\e[A" # Up | |
key_down "Up" | |
when "\e[B" # Down | |
key_down "Down" | |
when "\e[C" # Right | |
key_down "Right" | |
when "\e[D" # Left | |
key_down "Left" | |
when "\177" # Backspace | |
handle_backspace | |
when "\004" # Delete | |
key_down "Back" | |
when "\e[3~" # Alternative delete | |
key_down "ALT DELETE" | |
when "\u0003" # Control + C | |
puts @exit_msg | |
exit 0 | |
when /^.$/ # Single character: | |
character = c.inspect.gsub!(/^\"|\"?$/, '') | |
if @repeat_sequence == false | |
char_down character | |
else | |
compute_number character | |
print_repeat_value | |
end | |
else | |
puts "SOMETHING ELSE: #{c.inspect}" | |
end | |
end | |
# "\033[F" # Back one line | |
print "\n" | |
print_dash | |
print "\n Device IP: #{@device_ip}\n" | |
print_dash | |
# -------------------------------------------------------------------------------------- | |
puts %( | |
Sequences: Keys: Shortcuts: | |
1 . Reboot h . Home i . Info d . Dev Panel | |
2 . Secret Screen y . InstantReplay s . Search q . Quit | |
3 . Secret Screen 2 p . Play [Arrows] r . Repeat last [x] keys | |
4 . Token install screen j . Remote JS debugging | |
5 . Developer settings [<] [,] ........... Rewind [ . Slower sequences | |
6 . Channel info [>] [.] ........... Forward ] . Faster sequences | |
[return] [space] .. Select k . Clear history | |
[ESC] [backspace] . Back | |
) | |
print_dash | |
print "\n " | |
show_single_key while(true) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment