-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.htm
334 lines (301 loc) · 15.9 KB
/
index.htm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
<html>
<head>
<title>myremoku</title>
<script>
// Just use remoku.tv ?
// HOWEVER, be sure to add remoku.tv as an exception at chrome://settings/content/insecureContent
// For more details see https://stackoverflow.com/questions/73875589/disable-website-redirection-to-https-on-chrome
//
// INTERESTING! https://adtechmadness.wordpress.com/2020/11/01/attacking-roku-sticks-for-fun-and-profit/
//
//window.location = "http://remoku.tv";
// ON SECOND THOUGHT, remoku.tv sucks at sending keypresses to the roku
// Roku ECP API
// https://developer.roku.com/docs/developer-program/dev-tools/external-control-api.md
// FYI: The localStorage key "rokuAddreses43" has "43" at the end just to help ensure it is unique.
let LOCAL_STORAGE_KEY = "rokuAddresses43";
addEventListener("hashchange", (event) => {
console.log("START hashchange(), calling onBodyLoad()...");
onBodyLoad();
});
function onBodyLoad() {
console.log("START onBodyLoad()");
rokuAddresses.init();
// Handle changes to the select/dropdown list of possible roku ip addresses
const rokuSelect = document.getElementById("rokuSelect");
rokuSelect.onchange = function () {
const selectedOption = rokuSelect.options[rokuSelect.selectedIndex];
if (selectedOption.id == "selectMessage") {
return;
}
const selectedValue = selectedOption.value;
rokuAddresses.upsertAddresses(selectedValue, true);
window.location.hash = "#" + selectedValue;
};
// Capture keydown events and send them to the roku when appropriate
document.addEventListener("keydown", (e) => {
console.log(
`event.type=[${event.type}] .key=[${event.key}] .keyCode=[${event.keyCode}] .code=[${event.code}] .altKey=[${event.altKey}] .ctrlKey=[${event.ctrlKey}]`
);
if (event.metaKey || ["Shift", "Meta"].includes(event.key)) return;
// Roku Extended Control Protocol (ECP) documentation:
// https://developer.roku.com/docs/developer-program/debugging/external-control-api.md
// Keypress key values:
// https://developer.roku.com/docs/developer-program/debugging/external-control-api.md#keypress-key-values
var nonLitKeys = {
ArrowDown: "Down",
ArrowLeft: "Left",
ArrowUp: "Up",
ArrowRight: "Right",
Backspace: "Backspace",
Enter: "Select",
Home: "Home",
" ": "LIT_%20",
};
if (event.altKey || event.ctrlKey) {
if (event.key == "ArrowLeft") sendToRoku("Back", event);
if (event.key == "ArrowUp") sendToRoku("VolumeUp", event);
if (event.key == "ArrowDown") sendToRoku("VolumeDown", event);
} else sendToRoku(nonLitKeys[event.key] || "LIT_" + event.key, event);
});
}
function sendToRoku(keyPath, event, keyDir) {
var rokuIp = location.hash.slice(1);
var f = document.all.form4Posting;
f.action = "http://" + rokuIp + ":8060/" + (!keyDir ? "keypress" : keyDir) + "/" + keyPath;
console.log(`Doing form submit for action "${f.action}"`);
f.submit();
}
// Set up an object for managing possible roku addresses and the corresponding select element
const rokuAddresses = {
addresses: new Set(), // initialize an empty set for keeping track of roku addresses
init() {
// First, get any addresses that are already in local storage (or initialize local storage)
let storedAddresses;
if (localStorage.getItem(LOCAL_STORAGE_KEY)) {
storedAddresses = JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY));
} else {
storedAddresses = { addresses: [], selected: "" };
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(storedAddresses));
}
// Add those address from local storage to the UI's select/dropdown element
for (var a in storedAddresses.addresses) {
this.upsertAddresses(storedAddresses[a], storedAddresses[a] === storedAddresses.selected);
}
// Second, if there is a hash, presume it is an address and add it
if (location.hash.length > 1) {
this.upsertAddresses(location.hash.slice(1));
}
// Finally, scan for Rokus on the network so they can be added
this.scanForRokus();
},
upsertAddresses(address, selected) {
// If we don't already have this address...
if (!this.addresses.has(address)) {
// update the select element
const select = document.getElementById("rokuSelect");
const option = document.createElement("option");
option.value = address;
option.text = address;
if (selected) {
option.selected = "selected";
}
select.appendChild(option);
// at least one address has been added to the select dropdown, so hide the "searching" message
document.getElementById("searchingMessage").style.display = "none";
}
// Add a new address to the set
this.addresses.add(address);
// update local storage
localStorage.setItem(
LOCAL_STORAGE_KEY,
JSON.stringify({
addresses: Array.from(this.addresses).sort(),
selected: selected ? address : JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY)).selected,
})
);
},
scanForRokus() {
// The IP address ranges we will scan
const ipRangesToScan = [];
// Get the address, if any, that is in the location hash
if (location.hash.length > 1) {
// Use the address in the location hash to determine what ip addresses should be scanned
// TODO - use regex to get the first three parts of the address
if (location.hash.slice(1).startsWith("192.168.0")) {
ipRangesToScan.push("192.168.0");
} else {
ipRangesToScan.push("192.168.1");
}
} else {
ipRangesToScan.push("192.168.0");
ipRangesToScan.push("192.168.1");
}
// Scan local IP addresses for roku devices
console.log("Discovering Roku devices on the network...");
// The path to the device info endpoint
const path = "/query/device-info";
// An array to store the pending requests
const requests = [];
const errors = [];
let discovered = 0;
// Iterate through each IP address range and each IP address in the range, and add a request for each one to the pending requests array
for (const ipRange of ipRangesToScan) {
for (let i = 1; i <= 255; i++) {
const ipAddress = `${ipRange}.${i}`;
const url = `http://${ipAddress}:8060${path}`;
requests.push(
fetch(url, { timeout: 1000 })
.then((response) => {
// If the response is successful, add the device to the list
if (response.ok) {
console.log(`Discovered Roku device: ${ipAddress}`);
discovered++;
this.upsertAddresses(ipAddress);
} else {
errors.push(ipAddress + " response != ok " + JSON.stringify(response, undefined, 4));
}
})
.catch((error) => {
if (error.type === "opaque") {
// Handle CORS-related error
console.warning(`Potential CORS error for ${url}`);
} else {
// Handle other types of errors
// If there's an error (e.g. the device is not found or doesn't respond), ignore it and continue scanning
errors.push(ipAddress + " ERROR " + error.message);
}
})
);
}
}
// Send all of the requests in parallel and wait for all of them to complete before continuing
Promise.all(requests).then(() => {
console.log(`Discovered ${discovered} Roku devices on the network.`);
errors.sort();
console.warn(`Discovery errors by ip address:`, JSON.stringify(errors, undefined, 4));
});
},
};
</script>
<style>
div.btn {
font-size: xxx-large;
display: flex;
justify-content: center;
align-items: center;
height: 75px;
color: #dddddd;
}
span.btn {
cursor: pointer;
}
span.btn + span.btn {
margin-left: 75px;
}
</style>
</head>
<body bgcolor="#000000" onload="onBodyLoad()">
<div> </div>
<div style="text-align: center; color: white">
<p>Select a Roku on your network:</p>
<select name="rokuSelect" id="rokuSelect">
<option id="searchingMessage" disabled="true" style="display: none">Searching... (please wait)</option>
</select>
</div>
<div> </div>
<div> </div>
<div class="btn">
<span class="btn" onclick="sendToRoku('PowerOff')" title="Power Off">⌽</span>
</div>
<div class="btn">
<span class="btn" onclick="sendToRoku('Home')" title="Home">⌂</span>
<span class="btn" onclick="sendToRoku('VolumeUp')" title="Volume Up (Ctrl + Up Arrow)">🔊</span>
</div>
<div class="btn">
<span class="btn" onclick="sendToRoku('Back')" title="Back (Ctrl + Left Arrow)">←</span>
<span class="btn" onclick="sendToRoku('VolumeDown')" title="Volume Down (Ctrl + Down Arrow)">🔉</span>
</div>
<div class="btn">
<span class="btn" onclick="sendToRoku('Info',null,'keydown')" title="Info">ⓘ</span>
</div>
<div class="btn">
<span class="btn" onclick="sendToRoku('Rev')" title="Reverse">⏪️</span>
<span class="btn" onclick="sendToRoku('Play')" title="Play">▶️</span>
<span class="btn" onclick="sendToRoku('Fwd')" title="Forward">⏩️</span>
</div>
<iframe name="if4rokuPost" style="display: none"></iframe>
<form id="form4Posting" target="if4rokuPost" method="POST"></form>
<form id="form4Getting" target="if4rokuGet" method="GET"></form>
<div style="color: white">
<div style="display: inline-block; text-align: center">
<div style="width: 116px; height: 87px; overflow: hidden; display: inline-block">
<iframe
scrolling="no"
style="transform: scale(0.4); transform-origin: 0 0; width: 290px; height: 218px; border: 0"
src="http://192.168.0.157:8060/query/icon/2016"
></iframe>
</div>
<div style="width: 116px; display: flex; justify-content: center; align-items: center">hey hey</div>
</div>
<div style="display: inline-block; text-align: center">
<div style="width: 116px; height: 87px; overflow: hidden; display: inline-block">
<iframe
scrolling="no"
style="transform: scale(0.4); transform-origin: 0 0; width: 290px; height: 218px; border: 0"
src="http://192.168.0.157:8060/query/icon/2016"
></iframe>
</div>
<div style="width: 116px; display: flex; justify-content: center; align-items: center">hey hey</div>
</div>
<div style="display: inline-block; text-align: center">
<div style="width: 116px; height: 87px; overflow: hidden; display: inline-block">
<iframe
scrolling="no"
style="transform: scale(0.4); transform-origin: 0 0; width: 290px; height: 218px; border: 0"
src="http://192.168.0.157:8060/query/icon/2016"
></iframe>
</div>
<div style="width: 116px; display: flex; justify-content: center; align-items: center">hey hey</div>
</div>
</div>
<div style="background: white; color: black"><iframe src="http://192.168.0.157:8060/query/apps"></iframe></div>
<br><br><br><br>
<style>
.dropdown {
position: relative;
display: inline-block;
}
.dropdown-content {
display: none;
position: absolute;
background-color: #f9f9f9;
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
z-index: 1;
width: 300px; /* Set your desired width */
}
.dropdown-content iframe {
width: 100%;
height: 200px; /* Set your desired height */
border: none;
}
.dropdown:hover .dropdown-content {
display: block;
}
</style>
<div class="dropdown">
<button>Select an Option</button>
<div class="dropdown-content">
<div class="option">
<iframe src="https://www.example.com"></iframe>
</div>
<div class="option">
<iframe src="https://www.wikipedia.org"></iframe>
</div>
<div class="option">
<iframe src="https://www.openai.com"></iframe>
</div>
</div>
</div>
</body>
</html>