Compare commits

...

34 Commits

Author SHA1 Message Date
17e0e6e526 Merge branch 'dev' into feature/FixTv2 2026-05-05 02:15:34 +02:00
Ryder
02a3eef5e7 upload 2026-05-05 01:08:26 +01:00
zefie
11d2ab8c86 some documentation 2026-05-04 20:07:18 -04:00
zefie
778c0a2827 new ssl 2026-05-04 14:01:11 -04:00
zefie
00e385cdbe various fixups, allow FTP max size config 2026-05-04 08:38:22 -04:00
zefie
cf9cc22a1c new depreciation 2026-05-03 15:26:38 -04:00
zefie
0c5dc17ae6 create depreciation scanning tool 2026-05-03 15:23:23 -04:00
zefie
4347543ef7 don't expose version on pc services if hide_minisrv_config is true 2026-05-03 15:15:27 -04:00
zefie
9d51abd9ab documentation is hard 2026-05-03 15:10:44 -04:00
zefie
118443305b fix wrong key in quicksetup 2026-05-03 15:09:35 -04:00
zefie
ab4453487e more quicksetup 2026-05-03 15:07:30 -04:00
zefie
e003d9795b even more accurate QuickSetup.md 2026-05-03 14:59:14 -04:00
zefie
e88dbd98cc more accurate QuickSetup.md 2026-05-03 14:58:17 -04:00
zefie
eba447cd06 buncha stuff, v0.9.74 2026-05-03 14:51:59 -04:00
zefie
9aec2d3150 Update WTVClientSessionData
- Hash passwords before encrypting them for slightly extra security
  - Backwards compatible with previous 2 methods
- findAccountByUsername & setSSID - for MSNTV2 stuff
2026-05-03 14:14:43 -04:00
zefie
f2e11f827f switch mail to domain_name 2026-05-03 13:30:30 -04:00
zefie
32b6129ae3 set domain name to minisrv.local, check content-length on proxies 2026-05-03 13:07:12 -04:00
zefie
94e8ecb60a protect MSNTV2 proxy a little bit 2026-05-03 09:33:46 -04:00
zefie
1cf8032be4 remove comma 2026-05-02 18:08:52 -04:00
zefie
47033f737d passport service 2026-05-02 18:07:30 -04:00
zefie
0f85d8da56 stuff and things 2026-05-02 18:06:11 -04:00
zefie
31c2d94e0b idfk 2026-05-02 15:38:27 -04:00
zefie
0848f4a015 update readme 2026-05-02 11:51:16 -04:00
zefie
92958e4c64 somewhat guest login 2026-05-02 10:15:24 -04:00
zefie
4f20c08ed0 use UUID that can decode back to SSID 2026-05-01 19:21:19 -04:00
zefie
9400c1f917 actually create tv2 account 2026-05-01 19:07:47 -04:00
zefie
ddbcb4be5e msntv2 initial work 2026-05-01 18:10:25 -04:00
zefie
20357809a7 move 'hide_minisrv_version' from services to config 2026-04-30 02:15:58 -04:00
zefie
c1d730dffa image encoder/audio proxy only for webtv clients 2026-04-30 02:11:13 -04:00
zefie
1dfcf710ff experimental AudioProxy 2026-04-30 00:59:29 -04:00
zefie
d3ffd6c678 Smoother ALF/ALP 2026-04-29 14:45:04 -04:00
zefie
b294771061 Allow throttling sendToClient() 2026-04-28 23:11:06 -04:00
zefie
a058aa5095 ALP works now 2026-04-28 17:39:15 -04:00
zefie
fa459026ff LC2 telly-gen for minibrowser seems broken, at least on MAME, so bypass tellyscript for now 2026-04-27 15:41:53 -04:00
287 changed files with 21450 additions and 377 deletions

87
QuickSetup.md Normal file
View File

@@ -0,0 +1,87 @@
# Quick Setup
## user_config.json
`user_config.json` (in the same folder as `app.js`) is where you put your local configuration overrides. It merges on top of `includes/config.json`**do not edit `includes/config.json` directly**.
You only need to include keys you want to override. Copy `user_config.example.json` as a starting point, or start with a minimal file:
```json
{
"config": {
"service_ip": "192.168.1.x"
}
}
```
The file supports `// line comments` and `/* block comments */`.
---
## configurator.js
`tools/configurator.js` is a command-line tool that sets or deletes individual keys in `user_config.json` without manually editing JSON.
**Usage:**
```
node tools/configurator.js <dot.path.key> <value> [--overwrite]
node tools/configurator.js <dot.path.key> --delete [--overwrite]
```
- Use `--overwrite` to replace a key that already exists.
- Keys are expressed as dot-separated paths (e.g. `config.keys.user_data_key`).
---
## Setting service_ip
`service_ip` tells the box where to connect, this CANNOT be `0.0.0.0`, and must be an address reachable by your box when it connects via your setup. Can be `127.0.0.1` if you are running TouchPPP or WebTV Viewer on the same machine as minisrv.
```
node tools/configurator.js config.service_ip 192.168.1.x --overwrite
```
---
## Setting user_data_key
`user_data_key` is used to encrypt user data. It should be a random secret string and **must be set before registering any users**.
Changing it after users have registered will require updating the userdata with `tools/update_user_data_key.js`. Making a backup
of `SessionStore/accounts` is recommended before running `tools/update_user_data_key.js`, it is pretty resilent against corruption, but just in case.
```
node tools/configurator.js config.keys.user_data_key YOUR_RANDOM_SECRET --overwrite
```
To generate a random key:
```
openssl rand -base64 32
```
## Disabling a standard service
You can disable a configured service by setting the `disabled: true` flag for that service. For example, to disable `wtv-admin`:
```
node tools/configurator.js services.wtv-admin.disabled true
```
## Enabling a disabled service
You can disable a configured service by setting the `disabled: false` flag for that service. For example, to enable `pc_services`:
```
node tools/configurator.js services.pc_services.disabled false
```
## Custom service pages
You can place your custom pages in `UserServiceVault/servicename/page.js`. For example, to override `wtv-home:/home`, you would create
`UserServiceVault/wtv-home/home.js`, and the server will automatically prioritize your page. You can mix and match service vaults, accessing
resources in the standard service vault within your custom pages.
## Updating minisrv
You can `git pull`, or extract a new archive over the existing folder. If you followed the directions and kept your changes in `user_config.json` and `UserServiceVault`,
then you can update minisrv without worrying about breakage or losing data. Do pay attention to the console, and if any deprecreations appear, fix them before updating to the version listed in the notice.

View File

@@ -9,7 +9,7 @@ This open source server is in beta status. Use at your own risk.
### Note:
- This has nothing to do with streaming video.
- This is a server for the MSNTV (formerly WebTV) set-top box, and its various emulators.
- This does not support MSNTV2.
- This does not yet fully support MSNTV2.
### Current status:
- DB-less flat file client session store and registration system

View File

@@ -11,7 +11,7 @@ const process = wtvshared.process;
const util = wtvshared.util;
const nunjucks = require('nunjucks');
const {serialize, unserialize} = require('php-serialize');
const {spawn} = require('child_process');
const {spawn, spawnSync} = require('child_process');
const http = require('follow-redirects').http;
const httpx = require(classPath + "/HTTPX.js");
const { URL } = require('url');
@@ -27,6 +27,7 @@ const WTVClientSessionData = require(classPath + "/WTVClientSessionData.js");
const WTVMime = require(classPath + "/WTVMime.js");
const WTVFlashrom = require(classPath + "/WTVFlashrom.js");
const WTVImage = require(classPath + "/WTVImage.js");
const WTVAudioProxy = require(classPath + "/WTVAudioProxy.js");
const vm = require('vm');
const debug = require('debug')('app');
const express = require('express');
@@ -37,6 +38,30 @@ const protocolServers = [];
const minisrv_config = wtvshared.getMiniSrvConfig(); // snatches minisrv_config
const wtvmime = new WTVMime(minisrv_config);
const wtvAudioProxy = new WTVAudioProxy(minisrv_config);
function validateAudioProxy() {
if (!wtvAudioProxy || !wtvAudioProxy.isEnabled()) return;
try {
const check = spawnSync(wtvAudioProxy.config.ffmpegPath, ['-hide_banner', '-version'], {
stdio: ['ignore', 'ignore', 'ignore'],
timeout: 5000
});
if (check.error || check.status !== 0) {
console.warn(` # AudioProxy disabled: ffmpeg not found or failed to execute at '${wtvAudioProxy.config.ffmpegPath}'.`);
if (check.error) console.warn(` # AudioProxy ffmpeg error: ${check.error.message}`);
wtvAudioProxy.config.enabled = false;
return;
}
console.log(` * AudioProxy enabled: transcoding audio to ${wtvAudioProxy.config.bitrate} ${wtvAudioProxy.config.sampleRate}Hz mono MP3.`);
} catch (error) {
console.warn(` # AudioProxy disabled: ffmpeg validation failed: ${error.message}`);
wtvAudioProxy.config.enabled = false;
}
}
process
.on('SIGTERM', shutdown('SIGTERM'))
@@ -203,24 +228,43 @@ function getServiceString(service_name, overrides = {}) {
}
const DEPRECIATED_CONFIG_PATH = path.join(__dirname, 'includes', 'depreciated.json');
function loadDepreciatedPatterns() {
try {
if (!fs.existsSync(DEPRECIATED_CONFIG_PATH)) {
return {};
}
const raw = fs.readFileSync(DEPRECIATED_CONFIG_PATH, 'utf8');
const parsed = JSON.parse(raw);
if (!Array.isArray(parsed) || parsed.length === 0) {
return {};
}
const mapped = parsed
.filter((entry) => entry && typeof entry.pattern === 'string')
.map((entry) => ({
id: entry.id || entry.pattern,
pattern: new RegExp(entry.pattern, entry.flags || 'g'),
message: entry.message || 'Deprecated API usage found',
removeVersion: entry.removeVersion || 'unknown',
replacement: entry.replacement || null
}));
return mapped.length > 0 ? mapped : {};
} catch (error) {
console.warn('Failed to load includes/depreciated.json, using fallback deprecation patterns:', error.message);
return {};
}
}
// Deprecation warnings configuration
const deprecationWarnings = {
// Array of deprecated patterns with their details
patterns: [
{
// Example deprecations - you can modify these as needed
pattern: /session\_data\.hasCap\s*\(/g,
message: "session_data.hasCap() is deprecated and will be removed",
removeVersion: "0.9.80",
replacement: "Use session_data.capabilities.get() instead"
},
{
pattern: /(?<!wtvshared\.)getServiceString\s*\(/g,
message: "getServiceString() is deprecated and will be removed",
removeVersion: "0.9.80",
replacement: "Use wtvshared.getServiceString() instead"
}
],
patterns: loadDepreciatedPatterns(),
// Enable/disable deprecation warnings globally
enabled: true,
@@ -338,7 +382,7 @@ const runScriptInVM = function (script_data, user_contextObj = {}, privileged =
"service_vaults": service_vaults,
"service_deps": service_deps,
"ssid_sessions": ssid_sessions,
"moveArrayKey": wtvshared.moveArrayKey,
"moveArrayKey": wtvshared.moveArrayKey, // deprecated - use wtvshared.moveArrayKey() instead
"cwd": (filename) ? path.dirname(filename) : __dirname, // current working directory
// Our prototype overrides
@@ -468,7 +512,7 @@ async function handleCGI(executable, cgi_file, socket, request_headers, vault, s
env.SERVER_PORT = request_data.port;
env.SERVER_ADDR = request_data.host;
env.SERVER_NAME = request_data.host;
if ((minisrv_config.services[socket.service_name] && minisrv_config.services[socket.service_name].hide_minisrv_version) || minisrv_config.config.hide_server_version) {
if (minisrv_config.config.hide_minisrv_version) {
env.SERVER_SOFTWARE = "NodeJS; minisrv";
} else {
// Full version
@@ -1256,7 +1300,7 @@ minisrv-no-mail-count: true`;
}
}
async function sendToClient(socket, headers_obj, data = null) {
async function sendToClient(socket, headers_obj, data = null, throttle = 0) {
let headers = "";
let content_length = 0;
const eol = "\n";
@@ -1272,6 +1316,7 @@ async function sendToClient(socket, headers_obj, data = null) {
if (socket.destroy) socket.destroy();
return;
}
const isWebTVClient = Boolean(socket.ssid && ssid_sessions[socket.ssid]);
if (!socket.res) {
wtv_connection_close = (headers_obj["wtv-connection-close"]) ? true : false;
if (typeof (headers_obj["wtv-connection-close"]) !== 'undefined') delete headers_obj["wtv-connection-close"];
@@ -1367,24 +1412,22 @@ async function sendToClient(socket, headers_obj, data = null) {
delete headers_obj['minisrv-no-last-modified'];
}
if (minisrv_config.config.image_decoder && minisrv_config.config.image_decoder.enabled) {
if (isWebTVClient && minisrv_config.config.image_decoder && minisrv_config.config.image_decoder.enabled) {
const contype_key = wtvshared.getCaseInsensitiveKey('content-type', headers_obj);
let pngOpts = {};
if (contype_key) {
if (minisrv_config.config.image_decoder.image_formats && minisrv_config.config.image_decoder.image_formats.includes(headers_obj[contype_key].toLowerCase())) {
const convertOpts = {
jpegQuality: minisrv_config.config.image_decoder.jpg_quality,
type: imageArtemisType
type: imageArtemisType,
imgopts: minisrv_config.config.image_decoder.image_options || null
};
if (minisrv_config.config.image_decoder.max_height > 0) convertOpts.maxHeight = minisrv_config.config.image_decoder.max_height;
if (minisrv_config.config.image_decoder.max_width > 0) convertOpts.maxWidth = minisrv_config.config.image_decoder.max_width;
if (minisrv_config.config.image_decoder.png_opts) {
pngOpts = minisrv_config.config.image_decoder.png_opts;
}
const sourceData = Buffer.isBuffer(data) ? data : Buffer.from(data);
try {
const converted = await WTVImage.ImageToWebTV(sourceData, convertOpts, pngOpts);
const converted = await WTVImage.ImageToWebTV(sourceData, convertOpts);
data = converted.data;
content_length = data.length;
var i=0;
@@ -1392,7 +1435,7 @@ async function sendToClient(socket, headers_obj, data = null) {
// Image is too big, try to reduce quality
if (i < minisrv_config.config.image_decoder.max_quality_tries) {
convertOpts.jpegQuality -= minisrv_config.config.image_decoder.jpeg_interval;
var converted2 = await WTVImage.ImageToWebTV(sourceData, convertOpts);
var converted2 = await WTVImage.ImageToWebTV(sourceData, convertOpts, pngOpts);
data = converted2.data;
content_length = data.length;
i++;
@@ -1421,6 +1464,27 @@ async function sendToClient(socket, headers_obj, data = null) {
}
}
if (isWebTVClient && wtvAudioProxy && wtvAudioProxy.isEnabled()) {
const contype_key = wtvshared.getCaseInsensitiveKey('content-type', headers_obj);
if (contype_key && wtvAudioProxy.shouldProxy(headers_obj[contype_key])) {
try {
const transformResult = await wtvAudioProxy.transformIfNeeded(headers_obj, data);
headers_obj = transformResult.headers;
data = transformResult.data;
content_length = Buffer.isBuffer(data) ? data.length : Buffer.byteLength(data || '');
} catch (e) {
console.error('Audio proxy error:', e.message);
headers_obj = {
"Status": "413 Audio Too Long",
"Content-type": "text/plain"
};
data = (e.code === 'AUDIO_TOO_LONG') ?
`Audio exceeds maximum allowed duration of ${wtvAudioProxy.config.maxDurationSeconds} seconds.` :
`Audio proxy failure: ${e.message}`;
}
}
}
// if client can do compression, see if its worth enabling
// small files actually get larger, so don't compress them
@@ -1545,7 +1609,7 @@ async function sendToClient(socket, headers_obj, data = null) {
if (!xpower && socket.service_name) {
// add X-Powered-By header if not WebTV and not already set
xpower = 'X-Powered-By';
if (minisrv_config.services[socket.service_name].hide_minisrv_version) {
if (minisrv_config.config.hide_minisrv_version) {
// Don't report version
if (!socket.ssid) headers_obj[xpower] = "NodeJS; minisrv";
} else {
@@ -1556,7 +1620,7 @@ async function sendToClient(socket, headers_obj, data = null) {
// delete if webtv
if (socket.ssid) delete headers_obj[xpower];
if (socket.service_name) {
if (minisrv_config.services[socket.service_name].hide_minisrv_version) {
if (minisrv_config.config.hide_minisrv_version) {
// Don't report version
if (!socket.ssid) headers_obj[xpower] = headers_obj[xpower] + "; NodeJS; minisrv";
} else {
@@ -1614,64 +1678,98 @@ async function sendToClient(socket, headers_obj, data = null) {
let toClient = null;
if (typeof data === 'string') {
toClient = headers + eol + data;
sendToSocket(socket, Buffer.from(toClient));
sendToSocket(socket, Buffer.from(toClient), throttle);
} else if (typeof data === 'object') {
let verbosity_mod = (headers_obj["wtv-encrypted"] === 'true') ? " encrypted response" : "";
if (socket_sessions[socket.id].secure_headers === true) {
// encrypt headers
if (minisrv_config.config.debug_flags.quiet) verbosity_mod += " with encrypted headers";
const enc_headers = socket_sessions[socket.id].wtvsec.Encrypt(1, headers + eol);
sendToSocket(socket, new Buffer.from(concatArrayBuffer(enc_headers, data)));
sendToSocket(socket, new Buffer.from(concatArrayBuffer(enc_headers, data)), throttle);
} else {
sendToSocket(socket, new Buffer.from(concatArrayBuffer(Buffer.from(headers + eol), data)));
sendToSocket(socket, new Buffer.from(concatArrayBuffer(Buffer.from(headers + eol), data)), throttle);
}
if (minisrv_config.config.debug_flags.quiet) console.debug(" * Sent" + verbosity_mod + " " + headers_obj.Status + " to client (Content-Type:", headers_obj['Content-type'], "~", headers_obj['Content-length'], "bytes)");
}
}
}
async function sendToSocket(socket, data) {
async function sendToSocket(socket, data, throttle = 0) {
const chunk_size = 16384;
let can_write = true;
let close_socket = false;
let expected_data_out = 0;
while ((socket.bytesWritten === 0 || socket.bytesWritten !== expected_data_out) && can_write) {
if (expected_data_out === 0) expected_data_out = data.byteLength + (socket_sessions[socket.id].socket_total_written || 0);
if (socket.bytesWritten === expected_data_out) break;
if (!throttle || throttle <= 0) {
let can_write = true;
let close_socket = false;
let expected_data_out = 0;
while ((socket.bytesWritten === 0 || socket.bytesWritten !== expected_data_out) && can_write) {
if (expected_data_out === 0) expected_data_out = data.byteLength + (socket_sessions[socket.id].socket_total_written || 0);
if (socket.bytesWritten === expected_data_out) break;
const data_left = (expected_data_out - socket.bytesWritten);
// buffer size = lesser of chunk_size or size remaining
const buffer_size = (data_left >= chunk_size) ? chunk_size : data_left;
if (buffer_size < 0) {
socket.destroy();
close_socket = true;
break;
const data_left = (expected_data_out - socket.bytesWritten);
// buffer size = lesser of chunk_size or size remaining
const buffer_size = (data_left >= chunk_size) ? chunk_size : data_left;
if (buffer_size < 0) {
socket.destroy();
close_socket = true;
break;
}
const offset = (data.byteLength - data_left);
const chunk = new Buffer.alloc(buffer_size);
data.copy(chunk, 0, offset, (offset + buffer_size));
can_write = socket.write(chunk);
if (!can_write) {
socket.once('drain', function () {
sendToSocket(socket, data, throttle);
});
break;
}
}
const offset = (data.byteLength - data_left);
const chunk = new Buffer.alloc(buffer_size);
data.copy(chunk, 0, offset, (offset + buffer_size));
can_write = socket.write(chunk);
if (socket.bytesWritten === expected_data_out || close_socket) {
socket_sessions[socket.id].socket_total_written = socket.bytesWritten;
if (socket_sessions[socket.id].expecting_post_data) delete socket_sessions[socket.id].expecting_post_data;
if (socket_sessions[socket.id].header_buffer) delete socket_sessions[socket.id].header_buffer;
if (socket_sessions[socket.id].secure_buffer) delete socket_sessions[socket.id].secure_buffer;
if (socket_sessions[socket.id].buffer) delete socket_sessions[socket.id].buffer;
if (socket_sessions[socket.id].headers) delete socket_sessions[socket.id].headers;
if (socket_sessions[socket.id].post_data) delete socket_sessions[socket.id].post_data;
if (socket_sessions[socket.id].post_data_length) delete socket_sessions[socket.id].post_data_length;
if (socket_sessions[socket.id].post_data_percents_shown) delete socket_sessions[socket.id].post_data_percents_shown;
socket.setTimeout(minisrv_config.config.socket_timeout * 1000);
if (socket_sessions[socket.id] && socket_sessions[socket.id].close_me) socket.end();
if (socket_sessions[socket.id].destroy_me) socket.destroy();
}
return;
}
let offset = 0;
while (offset < data.byteLength) {
const buffer_size = Math.min(chunk_size, data.byteLength - offset);
const chunk = Buffer.alloc(buffer_size);
data.copy(chunk, 0, offset, offset + buffer_size);
offset += buffer_size;
const can_write = socket.write(chunk);
if (!can_write) {
socket.once('drain', function () {
sendToSocket(socket, data);
});
break;
await new Promise(resolve => socket.once('drain', resolve));
}
if (offset < data.byteLength) {
const delay_ms = Math.max(1, Math.round((buffer_size * 8) / throttle * 1000));
await new Promise(resolve => setTimeout(resolve, delay_ms));
}
}
if (socket.bytesWritten === expected_data_out || close_socket) {
socket_sessions[socket.id].socket_total_written = socket.bytesWritten;
if (socket_sessions[socket.id].expecting_post_data) delete socket_sessions[socket.id].expecting_post_data;
if (socket_sessions[socket.id].header_buffer) delete socket_sessions[socket.id].header_buffer;
if (socket_sessions[socket.id].secure_buffer) delete socket_sessions[socket.id].secure_buffer;
if (socket_sessions[socket.id].buffer) delete socket_sessions[socket.id].buffer;
if (socket_sessions[socket.id].headers) delete socket_sessions[socket.id].headers;
if (socket_sessions[socket.id].post_data) delete socket_sessions[socket.id].post_data;
if (socket_sessions[socket.id].post_data_length) delete socket_sessions[socket.id].post_data_length;
if (socket_sessions[socket.id].post_data_percents_shown) delete socket_sessions[socket.id].post_data_percents_shown;
socket.setTimeout(minisrv_config.config.socket_timeout * 1000);
if (socket_sessions[socket.id] && socket_sessions[socket.id].close_me) socket.end();
if (socket_sessions[socket.id].destroy_me) socket.destroy();
}
socket_sessions[socket.id].socket_total_written = socket.bytesWritten;
if (socket_sessions[socket.id].expecting_post_data) delete socket_sessions[socket.id].expecting_post_data;
if (socket_sessions[socket.id].header_buffer) delete socket_sessions[socket.id].header_buffer;
if (socket_sessions[socket.id].secure_buffer) delete socket_sessions[socket.id].secure_buffer;
if (socket_sessions[socket.id].buffer) delete socket_sessions[socket.id].buffer;
if (socket_sessions[socket.id].headers) delete socket_sessions[socket.id].headers;
if (socket_sessions[socket.id].post_data) delete socket_sessions[socket.id].post_data;
if (socket_sessions[socket.id].post_data_length) delete socket_sessions[socket.id].post_data_length;
if (socket_sessions[socket.id].post_data_percents_shown) delete socket_sessions[socket.id].post_data_percents_shown;
socket.setTimeout(minisrv_config.config.socket_timeout * 1000);
if (socket_sessions[socket.id] && socket_sessions[socket.id].close_me) socket.end();
if (socket_sessions[socket.id].destroy_me) socket.destroy();
}
function concatArrayBuffer(buffer1, buffer2) {
@@ -2406,6 +2504,8 @@ else console.log(" * Shenanigans disabled");
if (minisrv_config.config.image_decoder.enabled) console.log(" * WebTV Unsupported images will be processed and converted for WebTV clients");
else console.log(" * WebTV Unsupported images will not be processed, and sent to client as-is");
validateAudioProxy();
ports.sort();
pc_ports.sort();
@@ -2602,4 +2702,13 @@ if (bind_ports.length > 0) console.log(` * Started WTVP Server on port${bind_por
if (pc_bind_ports.length > 0) console.log(` * Started HTTP Server on port${pc_bind_ports.length !== 1 ? "s" : ""} ` + pc_bind_ports.join(", ") + "...");
if (protocolHandledPorts.size > 0) console.log(` * Started ${protocolHandledPorts.size} specialized protocol handler${protocolHandledPorts.size !== 1 ? "s" : ""} on port${protocolHandledPorts.size !== 1 ? "s" : ""} ` + [...protocolHandledPorts].map(([sn, sp, pt]) => `${pt} (${sp.toUpperCase()})`).join(", ") + "...");
const listening_ip_string = (minisrv_config.config.bind_ip !== "0.0.0.0") ? "IP: " + minisrv_config.config.bind_ip : "all interfaces";
console.log(" * Listening on", listening_ip_string, "~", "Service IP:", service_ip);
console.log(" * Listening on", listening_ip_string, "~", "Service IP:", service_ip);
// Security warning for default user data encryption key
if (minisrv_config.config.keys.user_data_key === "PNa$WN7gz}!T=t6X7^=|Ii##CEB~p\\EP") {
console.log(" * WARNING: You are using the default user data encryption key. This is not secure, and you should change it in the configuration file before registering any users.");
console.log(" * To generate a random key in bash or PowerShell, you can run: node ./tools/configurator.js config.keys.user_data_key $(openssl rand -base64 32)");
console.log(" * After changing the key in the user_config, please restart the server.");
console.log(" * If you had existing users prior to changing the key, you can run tools/update_user_data_key.js to update existing accounts with the new key.");
console.log(" * Making a backup of your user accounts before doing this is highly recommended, in case something goes wrong during the update process.");
}

View File

@@ -0,0 +1,23 @@
-----BEGIN CERTIFICATE-----
MIIDwzCCAqugAwIBAgIULL+4ofEIwgHwoGgoEJ+mJOmeF4YwDQYJKoZIhvcNAQEF
BQAwcDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk9IMRMwEQYDVQQHDApCdXR0IENy
YWNrMSAwHgYDVQQKDBdVbmRlcndlYXIgSW5zcGVjdG9yICMxMjEdMBsGA1UECwwU
VGhpbmcgTG9va2VyIEV4cGVydHMwIBcNMDAwNDIwMDAwMDAxWhgPMjA2OTA2MDkw
MDAwMDFaMHAxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJPSDETMBEGA1UEBwwKQnV0
dCBDcmFjazEgMB4GA1UECgwXVW5kZXJ3ZWFyIEluc3BlY3RvciAjMTIxHTAbBgNV
BAsMFFRoaW5nIExvb2tlciBFeHBlcnRzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEArN5xb8PqUKNNTitGuxyJJxEsWTIt7t7eobSq1+V3hTpknJEspRpb
JeD5Ep86pWn4aGoWYT0JU9w4FRJFPaBPAMmLFTIYv1bySlFwna+5J67dAtMm59Pr
t3ZSw2dQpads3LGZRSpkY1mv9GC2TVVk/TANWZmWYvEZZ4/2E2YuMJZ6v6gNIY7Y
pfJ6F6Q4+d/Dgrvow4E35lmNuM0Y95sdMhKy8HTJ0ecU/bMLKyoXXZyt7LxezT+T
w3J2EDVV03lDXZhb0h2ZECmpCAgnKsQ/WmLMjEGeojDQnm+QzTaFHqO0148RMbYN
lxURCZVl+keYe1LTHW/K+2f58DeeI0z11wIDAQABo1MwUTAdBgNVHQ4EFgQU0bsp
ZOhtJGP0dLS4sdaA4nmQ5ocwHwYDVR0jBBgwFoAU0bspZOhtJGP0dLS4sdaA4nmQ
5ocwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEANAV1WNeCggjK
eb8kIGkt2R3PMMBy2w+rC4X80xygh+NHU5neJKM1lpq6NLkt5rCBXNCTDb5uPjRv
mcrGeIYJYXMy39htjlYgPtfUiQx7e7eq3g1mMHYN26Gr9p7BwJW0QVvaA6zB3lqt
HGL7hwQyBCD9P/0sR91sjyLF9mPkaO4sBM5EbrV89LXbD+OPqn10X2Mu4trI3bd4
R02Pl0A/rDO/S7bOGc4HGGPSsmgeoapydvkqgnXL497zD1Sv103n96hiuq4Dhurj
gfnGxQvEN7MCc31HE2ufkTHjNc62RatseiCLxJE3sBhMl9aaNEhtM3FNDXZnhMbs
0/7C2b5kSg==
-----END CERTIFICATE-----

View File

@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCs3nFvw+pQo01O
K0a7HIknESxZMi3u3t6htKrX5XeFOmSckSylGlsl4PkSnzqlafhoahZhPQlT3DgV
EkU9oE8AyYsVMhi/VvJKUXCdr7knrt0C0ybn0+u3dlLDZ1Clp2zcsZlFKmRjWa/0
YLZNVWT9MA1ZmZZi8Rlnj/YTZi4wlnq/qA0hjtil8noXpDj538OCu+jDgTfmWY24
zRj3mx0yErLwdMnR5xT9swsrKhddnK3svF7NP5PDcnYQNVXTeUNdmFvSHZkQKakI
CCcqxD9aYsyMQZ6iMNCeb5DNNoUeo7TXjxExtg2XFREJlWX6R5h7UtMdb8r7Z/nw
N54jTPXXAgMBAAECggEAN5W7IfX8FPu9csIllyrAyygRe4jQDjvpmFNwj8VAHj29
dj6T/W6xGkfxrtQ2VlPxxRk/ovquUiHAgr4CN6OhY55qs2ENZhD+DXmMXZfQUHtA
o5TjsK2K4M4SJLTG0M06CuisYPoVl4CSPGEQnWM0+tiFombpXV0rDwrSVECJ36Mr
x/HPoMJQPewBSI4lG2ACQMBmQ+7WQVwl0mprJjIFJYW9BXFRkqmFKSXq6G302VcH
VOjiSOF/8BR64worbo9UrpPBPMLZMAW1rwZx50Chwhplben8B2uQNSqrc7Q9ZA3C
2XJ8Wi0KCeYmJy+xng3LT22Sdme1UC8YfC9iCsHfaQKBgQDe3sN97UK+WVIgvA8Q
+4+rpkUWOy/dCoTzPuxHy4qSTHyF723+URyVkNF/P5EtwKwpIuJ/WMwIyUF0EqLQ
0+6HrmqfO5CR+p/2xJhFPHi1qn3uCM8pw5O9i5MjhOr1jM1RQz1xe0rToLiTlq3x
sSrYzDmIs61kBH2NKaVp38Oh5QKBgQDGkObMw8Gyn5JT1ewkS0VGDT5bbVK5sptJ
KTkaikKTuNV5RLya+7SxnR7rOBWWLN+ZpdQygM8IlUh0TJiTZazUkKUSNv3isIbF
rfak10q8Zad6RkaQyyH7GKhBOnMs0z3kqgL0SvNhoIs2KKsE46UoMVE49/C9Ma8Y
zbdEBLTtCwKBgQCRJc934d/IDHAadZ/yVYOaLO1trxpbARDZQq+rinozEbE/oVGn
gbf2CJ0IHKQ2gfcdy2Rrv68SQdBpAgIbswr0Prmd/rMG/4zSr/LjlKCg3+qn7gDN
mFxN4+ruBRDo3syREhOgJsXy0gejx0x9zf6ztz35M9vG+c2y896Q93R3qQKBgA/e
AmN4fSD9+V5zqMQZs7ZuVn5N1R97s0b8YVDKnZlaWsyu/ndQB9dtm8vmFmuCuHd5
teQ3QNQJwdlxSXv10wLFcDttY4pa2tovFZeEkLdPVDvEI91sLhH3nXJg7lU1qCt+
nm/REXPKtXUleM0SN99nWXs47ObhcoGD5tIroh2TAoGBAISazlBBrmnNOavnUzWa
Ka2uXk/2txan8Fh9jul0g6GPPsI7Ej3YMf3ccC9iotVugteHrBUK0cNbesydw3J0
PIs05XLX0curxoTox2Jeynq+ajy2tnmrDHDfilXdxRt2rsQlobZi3qoJskwPFYhf
ssL5LnP78Pv/t2H01jDSpZXu
-----END PRIVATE KEY-----

View File

@@ -0,0 +1,27 @@
-----BEGIN CERTIFICATE-----
MIIEqzCCA5OgAwIBAgIQ3Zq6hcFrpKh4v3/G9sTw3DANBgkqhkiG9w0BAQUFADBw
MQswCQYDVQQGEwJVUzELMAkGA1UECAwCT0gxEzARBgNVBAcMCkJ1dHQgQ3JhY2sx
IDAeBgNVBAoMF1VuZGVyd2VhciBJbnNwZWN0b3IgIzEyMR0wGwYDVQQLDBRUaGlu
ZyBMb29rZXIgRXhwZXJ0czAgFw0wMDAxMDExMjAwMDBaGA8yMDk5MTIzMTIzNTk1
OVowUTEpMCcGA1UEAxMgaGVhZHdhaXRlci50cnVzdGVkLm1zbnR2Lm1zbi5jb20x
FzAVBgNVBAoTDlplZmllIE5ldHdvcmtzMQswCQYDVQQGEwJVUzCCASIwDQYJKoZI
hvcNAQEBBQADggEPADCCAQoCggEBAMtl9J068AFSHykffAlcpspq3D7mE7fFRoyf
+tQCM3Wy7PqJvDAegoI4Zf/QdToTJMS6dkcsEx+dgD01VKJ1B0RdHbg6rFQfymc4
GKyNk6tuqp7YQqElCUc91oFz4pJaJaOYaNBqkAG3MfTg+tSoBXl2YyjPrT0TPhXX
1Cm7BuFZORqNhvTdf33QXzgCQVso9U5X9YBgDaiTcu55etjFKUBEYhSYwTHmennA
FWOjY7ux6HFXBfKAz1QeCE6+corl5+6srCfh7Uz3ZFV9vntEYnyzbJuo6gR5P7GI
IYsygkADQAETHivl6GxeB7SEUfYLnfrZFwZc235tUz7USBdg3gcCAwEAAaOCAVww
ggFYMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMB
MIIBJwYDVR0RBIIBHjCCARqCIGhlYWR3YWl0ZXIudHJ1c3RlZC5tc250di5tc24u
Y29tghcqLnRydXN0ZWQubXNudHYubXNuLmNvbYINbXNudHYubXNuLmNvbYIWbWFp
bC5zZXJ2aWNlcy5saXZlLmNvbYIObG9naW4ubGl2ZS5jb22CEXBvcHRpbWl6ZS5t
c24uY29tghFmYXZvcml0ZXMubXNuLmNvbYIRbWVzc2VuZ2VyLm1zbi5jb22CEWxp
dmVmaWxlc3RvcmUuY29tghZ1c2Vycy5zdG9yYWdlLmxpdmUuY29tgglnLm1zbi5j
b22CF21zbmlhbG9naW4ucGFzc3BvcnQuY29tgg1taW5pc3J2LmxvY2Fsgg8qLm1p
bmlzcnYubG9jYWwwDQYJKoZIhvcNAQEFBQADggEBAAGEINTBTrkbpO0CJPv9w4Nj
IMuOSZETA7uXWyPwoLBIa57yTjNEVvWmjAc2nnrI3D6ijLMiF1eDIEsP4DI/qfMs
J82cS/IOIxXCmReU132NaZ6mSNEZx9QDkz/R8rFq5jKPRYSeguZSsWDxYlaQsbRr
qxQsKkRIOpm5pIOA/UT2gwV0L84a/NHXHNfc+CnPvvy7R9kmUC0XynsqU3lkj4Ah
SNZOgYyWkGWW7AytQWnMxyWm+xQjG4Fwl1Os9en4qwCK0ADyMCQyG3O68Gffu2go
YciXaJquI52fEKDQV4mDxy4B1V8BQ3ywm1iGebLzLgrKK7xPucUU5fqz7v2IUIs=
-----END CERTIFICATE-----

View File

@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAy2X0nTrwAVIfKR98CVymymrcPuYTt8VGjJ/61AIzdbLs+om8
MB6Cgjhl/9B1OhMkxLp2RywTH52APTVUonUHRF0duDqsVB/KZzgYrI2Tq26qnthC
oSUJRz3WgXPiklolo5ho0GqQAbcx9OD61KgFeXZjKM+tPRM+FdfUKbsG4Vk5Go2G
9N1/fdBfOAJBWyj1Tlf1gGANqJNy7nl62MUpQERiFJjBMeZ6ecAVY6Nju7HocVcF
8oDPVB4ITr5yiuXn7qysJ+HtTPdkVX2+e0RifLNsm6jqBHk/sYghizKCQANAARMe
K+XobF4HtIRR9gud+tkXBlzbfm1TPtRIF2DeBwIDAQABAoIBAADoPGfDhQfq4IyA
gVjRvhHmDLbTgOACp1aZbUPeNKUmpKVWniCOA+GZZ2V1ZEIOwGaH0sVtF8xXmZeF
5UUKOS7GLL7b6CJB6z2vNt3CJ35av03d3UI2iSzCEYxadd0jcMqJGfwsyLKkdgd6
rDO5m7ikdtT0qpGjEQi46AKkCZseSdIZqtgm1t0NupbXuBKbR13N6iNOr6eVnEpZ
tTrwIvw/OZ3Wf9tVW0DzjB1ejUYXCYAd6mHskMRrNfu/11x2pyXMHOSl24sVZxHe
Dxe+cYjw61/5pMMBq6ZQ61SPvAWZbIOA/PINHIUjisIGxpeqluz0zb+SLLrZnBe5
i9WEHsUCgYEA5lKEc5w02/n9+6wZCK/sPWhNJF3cPw2E0O+kqHPWLx2qPNhKf2Zh
8RycdVCnCcEXVQedYnNpBeAktMNGxAR9IhiVhfwiG0dFYXNUHBIHgVBZqtOeW8sS
uy4/fOw/qCCLWDfnDaozj8RnKsvu6zLm4c73Q4FEr4R6rk4EjfsGA90CgYEA4hMD
9PBdIWAYapP2FRangOmP50YNQVznrmDsfFlm2vh3aFrx83f0IFjYxeUI7OdTjkk3
7m5pEeV/liQwB/D7uXF+gNoUQab61QRhuKd58TbQELJkeBwevUb3rWVZ3LiFNNVB
vcunKTsL4qEADw56qMaczuZ27xXwatgUVtOdbTMCgYAmOA0ojgQreIlPyNgCnAas
jfE3Fqgkgl7GuO1u0oH5IYgNPqrmBxw7gU7pHLALK1Ju1qukGZiU1APjRwAoKMKG
9ONi71rNgf8eU5/iZI+AQtAOS71caA88pkj8tss9X+Efi2840kRqF+IytNJ5juHH
GKvpNLssEOS2pdWVpdDytQKBgF47WNne2qLPwD2kYN1XbQhh0lavL1VAWV2pDsmi
Jio9iOAZkGJQbJSTFAAgwICmx4A2arbalLcd9vlpKhAVVYdtlDI3NFxNMp5ZzjW+
sShnFYDwKsqZxgJVM2W2KifDTdrAzT9ERO/9qa2UOEcOXPg+mRvwmkB735NZb9jl
KehbAoGBANXDltYtJh28hMopP2sZUEx/4A2fnGjVMxBmpSsrk6irz5u/wdhimlDP
+JHZZXrb/OTU2BjCPjCCOj953XUqsCpBvWgGdX8uab0O4Gg8x8ucu/K6/kp0X0jN
EWQYaMAXJlLQM+k7bXrABNIGu8BF3JzC9oYjlKiFpLqNOQpfHdbK
-----END RSA PRIVATE KEY-----

View File

@@ -1,15 +1,17 @@
const minisrv_service_file = true;
const title = minisrv_config.config.hide_minisrv_version ? "zefie's minisrv PC Services" : `zefie minisrv v${minisrv_config.version} PC Services`;
headers = `200 OK
Content-Type: text/html`
data = `<html>
<head>
<title>zefie minisrv v${minisrv_config.version}</title>
<title>${title}</title>
</head>
<body bgcolor="#000000" text="#449944">
<p>
Welcome to the zefie minisrv v${minisrv_config.version} PC Services
Welcome to ${title}
</p>
<hr>
<a href="/viewergen/">WebTV Viewer Generator</a><br>

View File

@@ -0,0 +1,76 @@
const minisrv_service_file = true;
headers = `200 OK
Content-type: text/html`;
data = `<HTML>
<HEAD>
<title id="title"></title>
</HEAD>
<body>
<iframe id=checkmail style="display:none"></iframe>
<script language="javascript">
var tvShell = new ActiveXObject("MSNTV.TVShell");
function IsNightlyEnabled() {
var taskScheduler = tvShell.TaskScheduler;
var updateTask = null;
for (var i = 0; ((i < taskScheduler.Count) && (updateTask == null)); i++) {
if (taskScheduler.Item(i).Caller == 'NightlyUpdate') {
updateTask = taskScheduler.Item(i);
}
}
if (updateTask != null) {
return(true);
}
else {
return(false);
}
}
function GotoBoxCheck() {
var url = 'https://sg1.trusted.msntv.msn.com/connection/GatePage.aspx?phase=BoxCheck&purpose=Authorize';
var parms='';
parms += 'BoxId=' + tvShell.SystemInfo.BoxIDService + '&';
parms += 'WANProvider=' + tvShell.ConnectionManager.WANProvider + '&';
parms += 'version=' + encodeURIComponent(tvShell.SystemInfo.LastVersion) + '&';
if ((tvShell.ConnectionManager.MSNIAManager != null) && (tvShell.ConnectionManager.MSNIAManager.CurrentConnector != null)) parms += 'ConnectorName=' + encodeURIComponent(tvShell.ConnectionManager.MSNIAManager.CurrentConnector.Name) + '&';
if (tvShell.UserManager.CurrentUser != null) parms += 'domain=' + encodeURIComponent(tvShell.UserManager.CurrentUser.Domain) + '&';
parms += 'NumRedirects=0&';
parms += 'NightlyEnabled=' + IsNightlyEnabled() + '&';
parms += 'x=y';
var myPanel = tvShell.PanelManager.Item('service');
if (myPanel) myPanel.PostToURL(url, parms);
}
var progressPanel = tvShell.PanelManager.Item('progress');
function SetProgress(text, percent) {
if (progressPanel) {
progressPanel.Document.SetProgressText(text);
progressPanel.Document.SetProgressPercent(percent);
}
}
function IsServicePanel() {
if ((window.name == null) || ((window.name != null) && (window.name.toLowerCase() != 'service'))) {
return(false);
}
return(true);
}
function DontContinue() {
var currentUser = tvShell.UserManager.CurrentUser;
if (currentUser != null && currentUser.IsAuthorized) {
window.location.replace(tvShell.UserManager.CurrentUser.ServiceList.Item('home::home').URL);
}
else {
tvShell.ConnectionManager.ServiceState = 'ReSignIn';
}
}
if (!IsServicePanel()) {
DontContinue();
}
else {
tvShell.MeteringManager.Stop();
SetProgress('Please wait while we sign you into MSN TV.', 10);
GotoBoxCheck();
}
</script>
</body>
</HTML>
`;

View File

@@ -0,0 +1,444 @@
const minisrv_service_file = true;
const crypto = require('crypto');
// Sorry Zef :kek
// https://git.computernewb.com/yellows111/msnp-wiki/src/branch/master/docs/services/rst.md
// the RST_ cookie stuff was code that was temp until we had proper token authentication
const NS = {
SOAP: "http://schemas.xmlsoap.org/soap/envelope/",
WSSE: "http://schemas.xmlsoap.org/ws/2003/06/secext",
WSP: "http://schemas.xmlsoap.org/ws/2002/12/policy",
WSU: "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd",
WSA: "http://schemas.xmlsoap.org/ws/2004/03/addressing",
WST: "http://schemas.xmlsoap.org/ws/2004/04/trust",
PSF: "http://schemas.microsoft.com/Passport/SoapServices/SOAPFault",
ENC: "http://www.w3.org/2001/04/xmlenc#",
DS: "http://www.w3.org/2000/09/xmldsig#"
};
function getCookie(cookieString, name) {
if (!cookieString) return null;
const match = cookieString.match(new RegExp(`(?:^|;\\s*)${name}=([^;]*)`));
return match ? decodeURIComponent(match[1]) : null;
}
function setCookie(name, value, options = {}) {
const cookie = `${name}=${encodeURIComponent(value)}`;
const path = options.path || '/';
const expires = options.expires || '';
return `${cookie}; path=${path}${expires ? `; expires=${expires}` : ''}`;
}
function formatDateTime(dt) {
return dt.toISOString().replace(/\.\d{3}Z$/, 'Z');
}
function getClientIP() {
const forwarded = request_headers['x-forwarded-for'];
if (forwarded) {
const ips = forwarded.split(',');
return ips[0].trim();
}
return request_headers['x-real-ip'] || '127.0.0.1';
}
function generateRandomToken(userId, appliesTo, isLegacy = false) {
const timestamp = Date.now();
const randomPart = crypto.randomBytes(32).toString('hex');
if (isLegacy) {
const tokenData = `${userId}|${appliesTo}|${timestamp}|${randomPart}`;
return crypto.createHash('sha256').update(tokenData).digest('hex');
} else {
const tokenData = {
uid: userId,
app: appliesTo,
ts: timestamp,
rand: randomPart,
ver: '1.0'
};
return Buffer.from(JSON.stringify(tokenData)).toString('base64');
}
}
function extractXmlValue(xml, elementName) {
if (!xml) return null;
const patterns = [
new RegExp(`<${elementName}>([\\s\\S]*?)</${elementName}>`, 'i'),
new RegExp(`<wsse:${elementName}>([\\s\\S]*?)</wsse:${elementName}>`, 'i'),
new RegExp(`<wst:${elementName}>([\\s\\S]*?)</wst:${elementName}>`, 'i'),
new RegExp(`<ps:${elementName}>([\\s\\S]*?)</ps:${elementName}>`, 'i')
];
for (const regex of patterns) {
const match = xml.match(regex);
if (match && match[1]) {
let value = match[1].trim();
value = value.replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&amp;/g, '&');
return value;
}
}
return null;
}
function extractTokenFromCipherValue(xml) {
if (!xml) return null;
const cipherRegex = /<CipherValue>([\s\S]*?)<\/CipherValue>/gi;
let match;
let token = null;
while ((match = cipherRegex.exec(xml)) !== null) {
let cipherValue = match[1].trim();
if (cipherValue && cipherValue.length > 0) {
token = cipherValue;
console.log("Found CipherValue token:", token.substring(0, 50) + "...");
break;
}
}
return token;
}
function validateTokenAndGetUser(token) {
try {
let userId = null;
let email = null;
if (request_headers.cookie) {
userId = getCookie(request_headers.cookie, 'RST_Auth');
email = getCookie(request_headers.cookie, 'RST_Email');
if (!email) email = getCookie(request_headers.cookie, 'rst_email');
if (!email) email = getCookie(request_headers.cookie, 'rst_username');
}
if (!userId) {
userId = crypto.createHash('md5').update(token).digest('hex');
email = `user_${userId.substring(0, 8)}@example.com`;
}
console.log(`Token validated - UserId: ${userId}, Email: ${email}`);
return { success: true, userId, email };
} catch (error) {
console.error("Token validation error:", error);
return { success: false, userId: null, email: null };
}
}
function generateErrorResponse(errorCode, errorText) {
const now = formatDateTime(new Date());
headers = `Status: 200 OK
Content-type: text/xml; charset=utf-8`;
return `<?xml version="1.0" encoding="utf-8"?>
<S:Envelope xmlns:S="${NS.SOAP}" xmlns:psf="${NS.PSF}">
<S:Header>
<psf:pp>
<psf:serverVersion>1</psf:serverVersion>
<psf:authstate>0x80048800</psf:authstate>
<psf:reqstatus>${errorCode}</psf:reqstatus>
<psf:serverInfo Path="Live1" RollingUpgradeState="ExclusiveNew" LocVersion="0" ServerTime="${now}">
NOBELLIUM 16.0.30846.6
</psf:serverInfo>
<psf:cookies></psf:cookies>
<psf:response></psf:response>
</psf:pp>
</S:Header>
<S:Body>
<S:Fault>
<S:Code>
<S:Value>S:Sender</S:Value>
<S:Subcode>
<S:Value>wst:FailedAuthentication</S:Value>
</S:Subcode>
</S:Code>
<S:Reason>
<S:Text xml:lang="en-US">Authentication Failure</S:Text>
</S:Reason>
<S:Detail>
<psf:error>
<psf:value>${errorCode}</psf:value>
<psf:internalerror>
<psf:code>0x80041012</psf:code>
<psf:text>${errorText}</psf:text>
</psf:internalerror>
</psf:error>
</S:Detail>
</S:Fault>
</S:Body>
</S:Envelope>`;
}
function generateSuccessResponse(requestBody, userId, email, firstName, lastName) {
const now = new Date();
const tomorrow = new Date(now.getTime() + 24 * 60 * 60 * 1000);
const createdTime = formatDateTime(now);
const expiresTime = formatDateTime(tomorrow);
const puid = crypto.randomBytes(16).toString('hex').toUpperCase();
const cid = crypto.randomBytes(8).toString('hex').toUpperCase();
const safeFirstName = firstName || email.split('@')[0] || "User";
const safeLastName = lastName || "User";
const clientIp = getClientIP();
const rstRegex = /<wst:RequestSecurityToken[\s\S]*?<\/wst:RequestSecurityToken>/gi;
const responses = [];
let match;
let foundRst = false;
let rstIndex = 0;
while ((match = rstRegex.exec(requestBody)) !== null) {
foundRst = true;
const rstBlock = match[0];
const addressMatch = rstBlock.match(/<wsa:Address>(.*?)<\/wsa:Address>/i);
let appliesTo = addressMatch ? addressMatch[1] : "urn:passport:compact";
const policyMatch = rstBlock.match(/<wsse:PolicyReference\s+URI="([^"]+)"/i);
const policy = policyMatch ? policyMatch[1] : null;
const isLegacy = appliesTo.includes("Passport.NET");
const tokenType = isLegacy ? "urn:passport:legacy" : "urn:passport:compact";
const needsProofToken = policy === "MBI_KEY_OLD";
const token = generateRandomToken(userId, appliesTo, isLegacy);
const tokenId = isLegacy ? `BinaryDAToken${rstIndex}` : `Compact${rstIndex}`;
const binarySecret = crypto.randomBytes(32).toString('base64');
let requestedSecurityToken;
if (isLegacy) {
requestedSecurityToken = `
<wst:RequestedSecurityToken>
<EncryptedData xmlns="${NS.ENC}" Id="${tokenId}" Type="http://www.w3.org/2001/04/xmlenc#Element">
<EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#tripledes-cbc"/>
<ds:KeyInfo xmlns:ds="${NS.DS}">
<ds:KeyName>http://Passport.NET/STS</ds:KeyName>
</ds:KeyInfo>
<CipherData>
<CipherValue>${token}</CipherValue>
</CipherData>
</EncryptedData>
</wst:RequestedSecurityToken>`;
} else {
let tokenValue = `t=${token}`;
if (needsProofToken) {
tokenValue += `&p=profile`;
}
requestedSecurityToken = `
<wst:RequestedSecurityToken>
<wsse:BinarySecurityToken Id="${tokenId}">${tokenValue}</wsse:BinarySecurityToken>
</wst:RequestedSecurityToken>`;
}
let responseXml = `
<wst:RequestSecurityTokenResponse>
<wst:TokenType>${tokenType}</wst:TokenType>
<wsp:AppliesTo xmlns:wsa="${NS.WSA}">
<wsa:EndpointReference>
<wsa:Address>${appliesTo}</wsa:Address>
</wsa:EndpointReference>
</wsp:AppliesTo>
<wst:LifeTime>
<wsu:Created>${createdTime}</wsu:Created>
<wsu:Expires>${expiresTime}</wsu:Expires>
</wst:LifeTime>
${requestedSecurityToken}
<wst:RequestedTokenReference>
<wsse:KeyIdentifier ValueType="${tokenType}"/>
<wsse:Reference URI="#${tokenId}"/>
</wst:RequestedTokenReference>`;
if (needsProofToken || isLegacy) {
responseXml += `
<wst:RequestedProofToken>
<wst:BinarySecret>${binarySecret}</wst:BinarySecret>
</wst:RequestedProofToken>`;
}
responseXml += `
</wst:RequestSecurityTokenResponse>`;
responses.push(responseXml);
rstIndex++;
}
if (!foundRst) {
const defaultToken = generateRandomToken(userId, "urn:passport:compact", false);
responses.push(`
<wst:RequestSecurityTokenResponse>
<wst:TokenType>urn:passport:compact</wst:TokenType>
<wst:RequestedSecurityToken>
<wsse:BinarySecurityToken Id="Compact0">t=${defaultToken}</wsse:BinarySecurityToken>
</wst:RequestedSecurityToken>
<wst:LifeTime>
<wsu:Created>${createdTime}</wsu:Created>
<wsu:Expires>${expiresTime}</wsu:Expires>
</wst:LifeTime>
</wst:RequestSecurityTokenResponse>`);
}
headers = `Status: 200 OK
Content-type: text/xml; charset=utf-8
Set-Cookie: RST_Auth=${userId}; path=/; HttpOnly
Set-Cookie: RST_Email=${email}; path=/`;
return `<?xml version="1.0" encoding="utf-8"?>
<S:Envelope xmlns:S="${NS.SOAP}">
<S:Header>
<psf:pp xmlns:psf="${NS.PSF}">
<psf:serverVersion>1</psf:serverVersion>
<psf:PUID>${puid}</psf:PUID>
<psf:configVersion>16.000.26889.00</psf:configVersion>
<psf:uiVersion>3.100.2179.0</psf:uiVersion>
<psf:mobileConfigVersion>16.000.26208.0</psf:mobileConfigVersion>
<psf:authstate>0x48803</psf:authstate>
<psf:reqstatus>0x0</psf:reqstatus>
<psf:serverInfo Path="Live1" RollingUpgradeState="ExclusiveNew" LocVersion="0" ServerTime="${now.toISOString()}">
NOBELLIUM 16.0.30846.6
</psf:serverInfo>
<psf:cookies></psf:cookies>
<psf:browserCookies>
<psf:browserCookie Name="MH" URL="http://www.msn.com">MSFT; path=/; domain=.msn.com; expires=Wed, 30-Dec-2037 16:00:00 GMT</psf:browserCookie>
<psf:browserCookie Name="MH" URL="http://www.live.com">MSFT; path=/; domain=.live.com; expires=Wed, 30-Dec-2037 16:00:00 GMT</psf:browserCookie>
</psf:browserCookies>
<psf:credProperties>
<psf:credProperty Name="MainBrandID">MSFT</psf:credProperty>
<psf:credProperty Name="IsWinLiveUser">true</psf:credProperty>
<psf:credProperty Name="CID">${cid}</psf:credProperty>
<psf:credProperty Name="AuthMembername">${email}</psf:credProperty>
<psf:credProperty Name="Country">US</psf:credProperty>
<psf:credProperty Name="Language">1033</psf:credProperty>
<psf:credProperty Name="FirstName">${safeFirstName}</psf:credProperty>
<psf:credProperty Name="LastName">${safeLastName}</psf:credProperty>
<psf:credProperty Name="Flags">40100643</psf:credProperty>
<psf:credProperty Name="IP">${clientIp}</psf:credProperty>
</psf:credProperties>
<psf:extProperties>
<psf:extProperty Name="CID">${cid}</psf:extProperty>
</psf:extProperties>
<psf:response></psf:response>
</psf:pp>
</S:Header>
<S:Body>
<wst:RequestSecurityTokenResponseCollection
xmlns:wst="${NS.WST}"
xmlns:wsse="${NS.WSSE}"
xmlns:wsu="${NS.WSU}"
xmlns:wsp="${NS.WSP}"
xmlns:psf="${NS.PSF}">
${responses.join('\n ')}
</wst:RequestSecurityTokenResponseCollection>
</S:Body>
</S:Envelope>`;
}
function rstHandler() {
try {
// Get POST data
let requestBody = '';
if (request_headers.post_data) {
if (Buffer.isBuffer(request_headers.post_data)) {
requestBody = request_headers.post_data.toString('utf8');
} else if (typeof request_headers.post_data === 'string') {
requestBody = request_headers.post_data;
} else if (typeof request_headers.post_data === 'object') {
requestBody = JSON.stringify(request_headers.post_data);
}
} else {
console.log("No post_data found. Available keys:", Object.keys(request_headers));
return generateErrorResponse("0x80048820", "No POST data received");
}
if (!requestBody || requestBody.trim() === '') {
console.log("Empty request body");
return generateErrorResponse("0x80048820", "Empty request body");
}
// Authentication
let email = extractXmlValue(requestBody, 'Username');
let password = extractXmlValue(requestBody, 'Password');
let userId = null;
let userEmail = null;
let firstName = "User";
let lastName = "User";
if ((!email || !password) && requestBody.includes('CipherValue')) {
console.log("No username/password found, trying token authentication...");
const token = extractTokenFromCipherValue(requestBody);
if (token) {
const tokenValidation = validateTokenAndGetUser(token);
if (tokenValidation.success) {
userId = tokenValidation.userId;
userEmail = tokenValidation.email;
console.log(`Token authentication successful for: ${userEmail} (${userId})`);
if (request_headers.cookie) {
const cookieEmail = getCookie(request_headers.cookie, 'RST_Email');
const cookieUsername = getCookie(request_headers.cookie, 'rst_username');
if (cookieEmail) userEmail = cookieEmail;
if (cookieUsername) firstName = cookieUsername;
}
} else {
console.log("Token validation failed");
return generateErrorResponse("0x80048821", "Invalid token");
}
} else {
console.log("No token found in CipherValue");
return generateErrorResponse("0x80048820", "Missing credentials/token");
}
}
else if (email && password) {
console.log(`Extracted - Email: ${email}, Password: ${password ? '***' : 'empty'}`);
if (email && email.indexOf('@') < 0) {
const domain = (minisrv_config && minisrv_config.config && minisrv_config.config.domain_name) || 'wtv.zefie.com';
email = `${email}@${domain}`;
}
userEmail = email;
firstName = email.split('@')[0];
userId = crypto.createHash('md5').update(email).digest('hex');
console.log(`Authentication successful for: ${userEmail} (${userId})`);
}
else {
console.log("Missing both credentials and token");
return generateErrorResponse("0x80048820", "Missing credentials/token");
}
if (!userId || !userEmail) {
console.log("Failed to get user identity");
return generateErrorResponse("0x80048821", "User identity not found");
}
const cookieHeaders = [
setCookie('rst_email', userEmail, { path: '/' }),
setCookie('rst_username', firstName, { path: '/' }),
setCookie('rst_authenticated', 'true', { path: '/', expires: 'Wed, 30-Dec-2037 16:00:00 GMT' })
];
const response = generateSuccessResponse(requestBody, userId, userEmail, firstName, lastName);
for (const cookie of cookieHeaders) {
headers += `\nSet-Cookie: ${cookie}`;
}
return response;
} catch (error) {
console.error("RST Handler Error:", error);
console.error("Error stack:", error.stack);
return generateErrorResponse("0x80048820", `Internal error: ${error.message}`);
}
}
let result = rstHandler();
if (result) {
data = result;
}

View File

@@ -0,0 +1,13 @@
const minisrv_service_file = true;
// Wrong email return: <LoginResponse Success="false"><Error Code="e5b"/></LoginResponse>
// Wrong Password return: <LoginResponse Success="false"><Error Code="e5a"/></LoginResponse>
// Example Client request: <LoginRequest><ClientInfo name="MSNTV" version="1.35"/><User><SignInName>example@example.com</SignInName><Password>example</Password><SavePassword>false</SavePassword></User><DAOption>1</DAOption><TargetOption>1</TargetOption></LoginRequest>
data = `<LoginResponse Success="true"><TnP>t=Disabled&amp;p=Disabled</TnP></LoginResponse>`; // T and P cant be nulled they have to have some content in it
headers = `200 OK
Content-Type: text/xml`;
console.log(request_headers.query);

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 B

View File

@@ -0,0 +1,528 @@
const minisrv_service_file = true;
// Weather placeholder (static defaults — no live API)
const WeatherCity = 'Your City';
const WeatherTemp = '72';
const WeatherDescription = 'Sunny';
const WeatherIcon = '/Home/Weather/26.gif';
// News headlines
const NewsLink1 = '';
const NewsLink2 = '';
const NewsLink3 = '';
const NewsTitle1 = '...';
const NewsTitle2 = '...';
const NewsTitle3 = '...';
headers = `200 OK
Content-type: text/html`;
data = `<html xmlns:msntv>
<?import namespace="msntv" implementation="Shared/Anduril/HTC/en-us/TruncatedHtml.htc"?>
<head><title>Home</title>
<?import namespace="msntv" implementation="HTC/Shared/CustomButton.htc"?>
<?import namespace="msntv" implementation="/Home/Shared/BaseClient/HTCTransforms/en-us/LoopingDIV.htc"?>
<script src="/Include/2.0.261.900/localhost-1700/Shared/BaseClient/JsTransforms/en-us/PaneHelp.js" language="javascript" defer="true"></script>
<link href="/Include/2.0.261.778/localhost-1700/Home/Anduril/CssTransforms/en-us/Home.css" type="text/css" rel="StyleSheet">
<script src="/Include/2.0.261.778/localhost-1700/Home/Anduril/JsTransforms/en-us/Home.js" language="javascript"></script>
<script>
var forceReload = false;
var l = 'd:' + new Date().valueOf() + '|';
function setCookie(name, value) {
{
var now = new Date();
var expires = new Date(now.getFullYear() + 1, now.getMonth(), now.getDate());
document.cookie = escape(name) + '=' + escape(value) + ';expires=' + expires.toGMTString() + ';path=/';
}
}
function getCookie(name) {
{
var str = document.cookie;
var arr = str.split('; ');
for (var i = arr.length - 1; i >= 0; i--) {
{
var c = arr[i].split('=');
if (c.length != 2)
continue;
if (unescape(c[0]) == name)
return unescape(c[1]);
}
}
return null;
}
}
function syncCookie(cookieName, propValue) {
{
var c = getCookie(cookieName);
l += 'g:' + cookieName + ':' + c + '|';
if (c != propValue) {
{
setCookie(cookieName, propValue);
l += 's:' + cookieName + ':' + propValue + '|';
var check = getCookie(cookieName);
if (check == propValue)
forceReload = true;
}
}
}
}
var d = new Date();
var utcOffset = d.getTimezoneOffset();
syncCookie('UserUtcOffset', utcOffset);
var connSpeed;
try { { connSpeed = window.external.ClientCaps.connectionType; } }
catch (e) {
{
connSpeed = "undetected";
}
}
syncCookie('UserConnectionSpeed', connSpeed);
try {
{
top.log(l);
}
}
catch (e) {
{
}
}
if (forceReload)
location.replace(location.href);
</script></head>
<body>
<body onload="initPage();" scroll="no" tabindex="-1">
<div id="focdiv" style="position:absolute;top:314px;left:27px;width:70px;height:70px;" onclick="goToMail();">
</div>
<script>
document.all["focdiv"].focus();
function handleLoopingDivReady()
{
document.all["focdiv"].style.display = "none";
}
</script>
<div class="topdiv">
<div class="textMenu">
<script xmlns:msntvuxp="msntvuxp.microsoft.com">
function goToService(serviceName) {window.location = window.external.SafeGetServiceURL(serviceName);}
function goToCenter(svcURL) {window.location = svcURL;}
</script>
<div style="position:absolute; left:0px; top:0px" xmlns:msntvuxp="msntvuxp.microsoft.com">
<table border="0" class="TextMenuTbl">
<tr height="34" style="background-color: transparent;">
<td class="leftGradientTD"></td><td class="rightGradientTD">
</td>
</tr>
</table>
</div>
<div style="position:absolute; left:0px; top:0px" xmlns:msntvuxp="msntvuxp.microsoft.com">
<table border="0" class="TextMenuTbl">
<tr>
<td style="background-color: transparent;" width="8">
<img height="1" src="/Images/Shared/s.gif" width="8">
</td>
<td style="background-color: transparent;" align="left" width="162">
<a class="TextMenuLink" href="javascript:goToCenter('/Pages/UsingMSNTV/Main');" id="UsingMSNTVLinkID" onblur="umtvHasFocus=false">Using MSN TV</a>
</td>
<div>
<span style="position:absolute;left:0;top:35px;width:100%;height:2px;background-color:#8fc3d6;">
</span>
</div>
<td style="background-color: transparent;" align="left" width="115">
<a class="TextMenuLink" href="javascript:void(window.open(' ', 'signout', 'msntv:panel'));" id="SignoutLinkID">Sign Out</a>
</td>
<div>
<span style="position:absolute;left:0;top:35px;width:100%;height:2px;background-color:#8fc3d6;"></span>
</div>
<td style="background-color: transparent;" align="left" width="106">
<a class="TextMenuLink" href="javascript:goToService('UAM::UAMbase');" id="AccountLinkID">Account</a>
</td>
<div>
<span style="position:absolute;left:0;top:35px;width:100%;height:2px;background-color:#8fc3d6;">
</span>
</div>
<td style="background-color: transparent;" align="left" width="109">
<a class="TextMenuLink" href="javascript:goToService('settings::mainindex');" id="SettingsLinkID">Settings</a>
</td>
<div><span style="position:absolute;left:0;top:35px;width:100%;height:2px;background-color:#8fc3d6;">
</span>
</div>
<td style="background-color: transparent;" align="left" width="78">
<a href='javascript:CallPaneHelp(PH_TOC);' id="HelpLinkID">
<table><tr>
<td valign="middle" class="TextMenuLinkSimulation">Help</td>
<td valign="middle" width="30" height="20">
<span id="helpIcon" style='src:url(msntv:/Shared/Images/Icon_Help_RelatedLink.png);'></span>
</td>
</tr>
</table>
</a>
</td>
<div>
<span style="position:absolute;left:0;top:35px;width:100%;height:2px;background-color:#8fc3d6;">
</span>
</div>
</tr>
</table>
</div>
</div>
<div class="infoPaneDiv">
<div class="promoImgDiv">
<div style="position:absolute; left:0px; top:0px">
<img id="PromoImageID" width="178" height="135" border="0" hspace="0" alt="Promotional Image" src="/Home/ads/webtv3.gif">
</div>
<div style="position:absolute; left:5px; top:0px"><a id="PromoImageLinkID" href="">
<table width="173" height="135"><tr><td></td></tr></table></a>
</div>
</div>
<div id="timerRotatorDiv" class="personalPanelDiv" onclick="ClickRotator()">
<div>
<div style="top:0px; left:0px; width:176px; height:105px;" xmlns:msntvuxp="msntvuxp.microsoft.com">
<div class="PNGImage" style="width:176px;height:105px;src:/images/Home/HomeRotatorBGWeather.png;"></div>
</div>
<div style="position:absolute; top:0px; left:0px; width:178px; height:107px;" xmlns:msntvuxp="msntvuxp.microsoft.com">
<table class="wthrTbl" border="0" cellpadding="1" cellspacing="0">
<tr>
<td height="4" width="4" rowspan="4"><img src="images/Shared/s.gif" height="4" width="4"></td>
<td height="4" width="45"><img src="images/Shared/s.gif" height="4" width="45"></td>
<td height="4" width="10"><img src="images/Shared/s.gif" height="4" width="10"></td>
<td height="4" width="65"><img src="images/Shared/s.gif" height="4" width="65"></td>
</tr>
<tr>
<td id="CityCellID" class="wthrCityCell" colspan="3" valign="top">
<span class="wthrCityText">${WeatherCity}</span>
</td>
</tr>
<tr>
<td id="TRCID" class="wthrTempCond">
<table>
<tr>
<td id="TemperatureCellID" class="wthrTempCell">
<span class="wthrTempTxt">${WeatherTemp}&#176;F</span>
</td>
</tr>
<tr>
<td id="ConditionCellID" class="wthrCondCell">
<span class="wthrCondTxt">${WeatherDescription}</span>
</td>
</tr>
</table>
</td>
<td id="PaddingID" width="10"><img src="images/Shared/s.gif" height="1" width="10"></td>
<td id="ConditionIconID" class="wthrCondIcon">
<img src="${WeatherIcon}" alt="Weather icon" style="width:65px;height:61px;">
</td>
</tr>
<tr>
<td id="ProviderID" class="wthrProvider" colspan="3">
Open-Meteo
</td>
</tr>
</table>
</div>
<script xmlns:msntvuxp="msntvuxp.microsoft.com">
function clickPageRotatePanel() {
location.href = "/Pages/weather/yourcity";
}
</script>
</div>
</div>
<script>
InitRotator(timerRotatorDiv, 5000, new Array("/home/MoneyModule.aspx","/Pages/weather/WeatherModule"));
</script>
<div ID="clockID" class="clockDiv">
</div>
<script>clockID.innerHTML = formClockLink();</script>
<div class="newsHdlnDiv">
<div class="newsHdlnTitleDiv"><span class="newsHdlnTitleText">Today on TV2</span></div>
<table style="top:0px;left:0px;width:365px;height:78px;">
<tr><td><a class="newsHdlnLink" id="newsHdlnLinkID1" href="${NewsLink1}">${NewsTitle1}</a></td></tr>
<tr><td><a class="newsHdlnLink" id="newsHdlnLinkID2" href="${NewsLink2}">${NewsTitle2}</a></td></tr>
<tr><td><a class="newsHdlnLink" id="newsHdlnLinkID3" href="${NewsLink3}">${NewsTitle3}</a></td></tr>
</table>
<div class="moreNewsDiv">
<table><tr>
<td><img src="msntv:/Shared/images/BulletCustom.gif" height="14" width="7"></td>
<td><img src="images/Shared/s.gif" height="1" width="4"></td>
<td style="height:37px"><a id="moreNewsLinkID" class="moreNewsLink" href="../pages/TopStories">More MSN news</a></td>
</tr></table>
</div>
</div>
<div class="promoPanelDiv">
<div style="position:absolute; width:355px; height:70px;">
<table style="width:365px;">
<td class="promoHdlnTitle" style="color:#1D704C">Using MSN TV</td>
<tr>
<td class="promoHdlnCell"><a class="promoHdlnLink" id="promoHdlnLinkID1" href="http://msntv.msn.com/pages/usingmsntv/tippage.aspx?id=set15">Tip: Turn on audible dialing </a></td></tr><tr>
<td class="promoHdlnCell"><a class="promoHdlnLink" id="promoHdlnLinkID2" href="http://msntv.msn.com/pages/UsingMSNTV/Article.aspx?id=feature3">Get better printing results</a></td></tr>
</table>
</div>
<div style="position:absolute; left:161px; top:74px; width:200px;">
<table class="MoreUsingLinkTable"><tr>
<td><img src="msntv:/Shared/Images/BulletCustom.gif" height="14" width="7"></td>
<td><img src="/Images/Shared/s.gif" height="1" width="4"></td>
<td>
<a id="MoreUsingLinkID" class="MoreNewsLink" href="http://headwaiter.trusted.msntv.msn.com/Pages/UsingMSNTV/Main">Go to Using MSN TV</a>
</td></tr>
</table>
</div>
</div>
</div>
<div id="searchID" class="searchDiv">
<div class="searchCenterDiv">
<script>
function doSearch(country){
if (searchFormID.searchFieldID.value == "")
{
window.location = window.external.SafeGetServiceURL('search::search') + "?FORM=WEBTV&cfg=MSTVXML&v=1&c="+country+"&x=26&y=14";
}
else
{
window.location = window.external.SafeGetServiceURL('search::search') + "?FORM=WEBTV&cfg=MSTVXML&v=1&c="+country+"&x=26&y=14&q=" + escape(searchFormID.searchFieldID.value);
}
}
</script>
<div style="position:absolute; z-index:1; left:0; top:0px; width:100%; height:2px; background-color:#0c7faa;">
<table style="width:100%; height:2px;"><tr><td height="2"></td></tr></table>
</div>
<table class="searchTbl">
<form id="searchFormID" action="javascript:doSearch('US')">
<tr>
<td width="10"></td>
<td><a class="searchLink" href="javascript:doSearch('US');">Search</a><span class="searchLabelText"> or type www</span></td>
<td></td>
<td><span class="searchFieldText"><input id="searchFieldID" name="searchFieldName" class="searchField" type="text" size="28"></span></td>
<td></td>
<td valign="center"><msntv:custombutton id="GoButtonID" onClick="doSearch('US');" label="Go" /></td>
</tr></form></table>
<div style="position:absolute; left:0; top:33px; width:100%; height:2px; background-color:#1c4373;">
<table style="width:100%; height:2px;"><tr><td height="2"></td></tr></table>
</div>
</div>
</div>
<div id="iconNavBarID" class="iconNavBar">
<table class="iconNavBarMasterTbl">
<tr>
<td align="center" valign="middle">
<table class="iconNavBarTbl">
<tr>
<td class="iconNavBarTblFrameCell">
<table class="ApolloIcons" xmlns:msntv="http://tv.msn.com">
<tr height="70">
<td>
<msntv:loopingDIV id="navbar" hasInitialFocus="true" divWidthPX="554" onLoopingDivReady="handleLoopingDivReady()" />
</td>
</tr>
<script>
var nIcons;
var ImgURL = new Array();
var ImgWidth = new Array();
var ImgOverURL = new Array();
var IconURL = new Array();
function goToFavorites() {
window.open(" ", "favoritespanel", "msntv:panel");
}
function goToMessenger() {
if (window.external.SafeGetServiceURL('messenger::root') != null && window.external.SafeGetServiceURL('messenger::root') != "") window.open(" ", "impanel", "msntv:panel");
else window.location = "msntv:/OLTK/IMBlock.html";
}
function goToMail() {
if (window.external.SafeGetServiceURL('mail::listmail') != null && window.external.SafeGetServiceURL('mail::listmail') != "") window.location = window.external.SafeGetServiceURL('mail::listmail');
else window.location = "msntv:/OLTK/EmailBlock.html";
}
function goToChat() {
if (window.external.SafeGetServiceURL('chat::home') != null && window.external.SafeGetServiceURL('chat::home') != "") window.location = window.external.SafeGetServiceURL('chat::home');
else window.location = "msntv:/OLTK/chatBlock.html";
}
function goToSearch() { window.location = window.external.SafeGetServiceURL('search::main'); }
function goToMaps() { window.location = window.external.SafeGetServiceURL('maps::main'); }
function goToMusicHome() { window.location = window.external.SafeGetServiceURL('Music::Home'); }
function goToVideoHome() { window.location = window.external.SafeGetServiceURL('Video::Home'); }
function goToNetwork() { window.location = window.external.SafeGetServiceURL('Settings::HomeNetwork'); }
function goToAccount() { window.location = window.external.SafeGetServiceURL('UAM::UAMbase'); }
function goToSettings() { window.location = window.external.SafeGetServiceURL('settings::mainindex'); }
function goToCenter(svcURL) { window.location = svcURL; }
function goToPhotosApp() { window.location = window.external.SafeGetServiceURL('Photos'); }
function goToPhotosHome() { window.location = window.external.SafeGetServiceURL('Photo::Home'); }
function initIcons() {
var dq = '"';
for (index = 0; index < nIcons; index++) {
var realIndex = (index + nIcons - 1) % nIcons;
var cellHTML = '<span' +
' onFocus=' + dq + 'ImgObjs' + realIndex + ".src='" + ImgOverURL[realIndex] + "';" + dq +
' onBlur=' + dq + 'ImgObjs' + realIndex + ".src='" + ImgURL[realIndex] + "';" + dq +
' onClick=' + dq + IconURL[realIndex] + dq +
'><img id=' + dq + 'ImgObjs' + realIndex + dq +
" src='" + ImgURL[realIndex] + "' width=" + ImgWidth[realIndex] + ' height=61 border=0></span>';
navbar.AddCellHTML(cellHTML, ImgWidth[realIndex]);
}
}
ImgURL[0] = "/Images/Home/HomeIconFav.jpg";
ImgOverURL[0] = "/Images/Home/HomeIconFavOver.jpg";
ImgWidth[0] = 87;
IconURL[0] = "javascript:goToFavorites();";
ImgURL[1] = "/Images/Home/HomeIconMail.jpg";
ImgOverURL[1] = "/Images/Home/HomeIconMailOver.jpg";
ImgWidth[1] = 70;
IconURL[1] = "javascript:goToMail();";
ImgURL[2] = "/Images/Home/HomeIconMsgr.jpg";
ImgOverURL[2] = "/Images/Home/HomeIconMsgrOver.jpg";
ImgWidth[2] = 99;
IconURL[2] = "javascript:goToMessenger();";
ImgURL[3] = "/Images/Home/HomeIconMaps.jpg";
ImgOverURL[3] = "/Images/Home/HomeIconMapsOver.jpg";
ImgWidth[3] = 60;
IconURL[3] = "javascript:goToMaps();";
ImgURL[4] = "/Images/Home/HomeIconPhoto.jpg";
ImgOverURL[4] = "/Images/Home/HomeIconPhotoOver.jpg";
ImgWidth[4] = 70;
IconURL[4] = "javascript:goToPhotosApp();";
ImgURL[5] = "/Images/Home/HomeIconVideos.jpg";
ImgOverURL[5] = "/Images/Home/HomeIconVideosOver.jpg";
ImgWidth[5] = 70;
IconURL[5] = "javascript:goToVideoHome();";
ImgURL[6] = "/Images/Home/HomeIconMusic.jpg";
ImgOverURL[6] = "/Images/Home/HomeIconMusicOver.jpg";
ImgWidth[6] = 66;
IconURL[6] = "javascript:goToMusicHome();";
ImgURL[7] = "/Images/Home/HomeIconDiscuss.jpg";
ImgOverURL[7] = "/Images/Home/HomeIconDiscussOver.jpg";
ImgWidth[7] = 72;
IconURL[7] = "javascript:goToCenter('/Pages/Discuss/Home.aspx');";
ImgURL[8] = "/Images/Home/HomeIconMSNBC.jpg";
ImgOverURL[8] = "/Images/Home/HomeIconMSNBCOver.jpg";
ImgWidth[8] = 68;
IconURL[8] = "javascript:goToCenter('/Pages/News/TopStories.aspx');";
ImgURL[9] = "/Images/Home/HomeIconTWC.jpg";
ImgOverURL[9] = "/Images/Home/HomeIconTWCOver.jpg";
ImgWidth[9] = 81;
IconURL[9] = "javascript:goToCenter('/Pages/Weather/MyCity.aspx');";
ImgURL[10] = "/Images/Home/HomeIconEnt.jpg";
ImgOverURL[10] = "/Images/Home/HomeIconEntOver.jpg";
ImgWidth[10] = 125;
IconURL[10] = "javascript:goToCenter('/Pages/Entertainment/Home.aspx');";
ImgURL[11] = "/Images/Home/HomeIconTVListings.jpg";
ImgOverURL[11] = "/Images/Home/HomeIconTVListingsOver.jpg";
ImgWidth[11] = 96;
IconURL[11] = "javascript:goToCenter('http://tv.msn.com/tv/guide');";
ImgURL[12] = "/Images/Home/HomeIconSports.jpg";
ImgOverURL[12] = "/Images/Home/HomeIconSportsOver.jpg";
ImgWidth[12] = 82;
IconURL[12] = "javascript:goToCenter('/Pages/Sports/Main.aspx');";
ImgURL[13] = "/Images/Home/HomeIconMoney.jpg";
ImgOverURL[13] = "/Images/Home/HomeIconMoneyOver.jpg";
ImgWidth[13] = 68;
IconURL[13] = "javascript:goToCenter('/Pages/Money/Home.aspx');";
ImgURL[14] = "/Images/Home/HomeIconShopping.jpg";
ImgOverURL[14] = "/Images/Home/HomeIconShoppingOver.jpg";
ImgWidth[14] = 62;
IconURL[14] = "javascript:goToCenter('/Pages/Shopping/Main.aspx');";
ImgURL[15] = "/Images/Home/HomeIconGames.jpg";
ImgOverURL[15] = "/Images/Home/HomeIconGamesOver.jpg";
ImgWidth[15] = 70;
IconURL[15] = "javascript:goToCenter('/Pages/Games/Home.aspx');";
ImgURL[16] = "/Images/Home/HomeIconEncarta.jpg";
ImgOverURL[16] = "/Images/Home/HomeIconEncartaOver.jpg";
ImgWidth[16] = 74;
IconURL[16] = "javascript:goToCenter('http://g.msn.com/5TVANDURIL/1505')";
ImgURL[17] = "/Images/Home/HomeIconChat.jpg";
ImgOverURL[17] = "/Images/Home/HomeIconChatOver.jpg";
ImgWidth[17] = 55;
IconURL[17] = "javascript:goToChat();";
ImgURL[18] = "/Images/Home/HomeIconUsingMSN.jpg";
ImgOverURL[18] = "/Images/Home/HomeIconUsingMSNOver.jpg";
ImgWidth[18] = 127;
IconURL[18] = "javascript:goToCenter('/Pages/UsingMSNTV/Main');";
ImgURL[19] = "/Images/Home/HomeIconTTT.jpg";
ImgOverURL[19] = "/Images/Home/HomeIconTTTOver.jpg";
ImgWidth[19] = 116;
IconURL[19] = "javascript:goToCenter('/Pages/UsingMSNTV/ThingstoTry');";
ImgURL[20] = "/Images/Home/HomeIconSearch.jpg";
ImgOverURL[20] = "/Images/Home/HomeIconSearchOver.jpg";
ImgWidth[20] = 84;
IconURL[20] = "javascript:goToSearch();";
ImgObjs = new Array();
ImgOverObjs = new Array();
nIcons = ImgURL.length;
ImgOverObjs[0] = new Image();
ImgOverObjs[0].src = ImgOverURL[0];
for (var i = 0; i < nIcons; i++) {
ImgObjs[i] = new Image();
ImgObjs[i].src = ImgURL[i];
}
for (var i = 1; i < nIcons; i++) {
ImgOverObjs[i] = new Image();
ImgOverObjs[i].src = ImgOverURL[i];
}
initIcons();
</script>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</div>
<script>
function callCGif()
{
var i = new Image();
i.src = "http://c.msn.com/c.gif?DI=1455&PI=68206&PS=45577&TP=http://msntv.msn.com/HomePage.htm&RF=";
}
window.attachEvent("onload", callCGif);
</script>
</div>
</body></html>`;

View File

@@ -0,0 +1,629 @@
@page "/Pages/Home/Home.aspx"
@using MSNTV2MainServer.Components.Layout
@using Microsoft.AspNetCore.Http
@using Microsoft.AspNetCore.Components
@using MSNTV2MainServer.Components.APIs
@inject IHttpContextAccessor httpContextAccessor
@layout EmptyLayout
@if (IsTV2)
{
MarkupString htmlContent = new MarkupString($@"
<html xmlns:msntv>
<?import namespace=""msntv"" implementation=""Shared/Anduril/HTC/en-us/TruncatedHtml.htc""?>
<head><title>Home</title>
<?import namespace=""msntv"" implementation=""HTC/Shared/CustomButton.htc""?>
<?import namespace=""msntv"" implementation=""/Pages/Home/Shared/BaseClient/HTCTransforms/en-us/LoopingDIV.htc""?>
<script src=""/Include/2.0.261.900/localhost-1700/Shared/BaseClient/JsTransforms/en-us/PaneHelp.js"" language=""javascript"" defer=""true""></script>
<link href=""/Include/2.0.261.778/localhost-1700/Home/Anduril/CssTransforms/en-us/Home.css"" type=""text/css"" rel=""StyleSheet"">
<script src=""/Include/2.0.261.778/localhost-1700/Home/Anduril/JsTransforms/en-us/Home.js"" language=""javascript""></script>
<script>
var forceReload = false;
var l = 'd:' + new Date().valueOf() + '|';
function setCookie(name, value) {{
{{
var now = new Date();
var expires = new Date(now.getFullYear() + 1, now.getMonth(), now.getDate());
document.cookie = escape(name) + '=' + escape(value) + ';expires=' + expires.toGMTString() + ';path=/';
}}
}}
function getCookie(name) {{
{{
var str = document.cookie;
var arr = str.split('; ');
for (var i = arr.length - 1; i >= 0; i--) {{
{{
var c = arr[i].split('=');
if (c.length != 2)
continue;
if (unescape(c[0]) == name)
return unescape(c[1]);
}}
}}
return null;
}}
}}
function syncCookie(cookieName, propValue) {{
{{
var c = getCookie(cookieName);
l += 'g:' + cookieName + ':' + c + '|';
if (c != propValue) {{
{{
setCookie(cookieName, propValue);
l += 's:' + cookieName + ':' + propValue + '|';
var check = getCookie(cookieName);
if (check == propValue)
forceReload = true;
}}
}}
}}
}}
var d = new Date();
var utcOffset = d.getTimezoneOffset();
syncCookie('UserUtcOffset', utcOffset);
var connSpeed;
try {{ {{ connSpeed = window.external.ClientCaps.connectionType; }} }}
catch (e) {{
{{
connSpeed = ""undetected"";
}}
}}
syncCookie('UserConnectionSpeed', connSpeed);
try {{
{{
top.log(l);
}}
}}
catch (e) {{
{{
}}
}}
if (forceReload)
location.replace(location.href);
</script></head>
<body>
<body onload=""initPage();"" scroll=""no"" tabindex=""-1"">
<div id=""focdiv"" style=""position:absolute;top:314px;left:27px;width:70px;height:70px;"" onclick=""goToMail();"">
</div>
<script>
document.all[""focdiv""].focus();
// when looping div is ready, you can hide the invisible rectangle used for holding Mail's
function handleLoopingDivReady()
{{
document.all[""focdiv""].style.display = ""none"";
}}
</script>
<div class=""topdiv"">
<div class=""textMenu"">
<script xmlns:msntvuxp=""msntvuxp.microsoft.com"">
function goToService(serviceName) {{window.location = window.external.SafeGetServiceURL(serviceName);}}
function goToCenter(URL) {{window.location = URL;}}
</script>
<div style=""position:absolute; left:0px; top:0px"" xmlns:msntvuxp=""msntvuxp.microsoft.com"">
<table border=""0"" class=""TextMenuTbl"">
<tr height=""34"" style=""background-color: transparent;"">
<td class=""leftGradientTD""></td><td class=""rightGradientTD"">
</td>
</tr>
</table>
</div>
<div style=""position:absolute; left:0px; top:0px"" xmlns:msntvuxp=""msntvuxp.microsoft.com"">
<table border=""0"" class=""TextMenuTbl"">
<tr>
<td style=""background-color: transparent;"" width=""8"">
<img height=""1"" src=""/Images/Shared/s.gif"" width=""8"">
</td>
<td style=""background-color: transparent;"" align=""left"" width=""162"">
<a class=""TextMenuLink"" href=""javascript:goToCenter('/Pages/UsingMSNTV/Main');"" id=""UsingMSNTVLinkID"" onblur=""umtvHasFocus=false"">Using MSN TV</a>
</td>
<div>
<span style=""position:absolute;left:0;top:35px;width:100%;height:2px;background-color:#8fc3d6;"">
</span>
</div>
<td style=""background-color: transparent;"" align=""left"" width=""115"">
<a class=""TextMenuLink"" href=""javascript:void(window.open(' ', 'signout', 'msntv:panel'));"" id=""SignoutLinkID"">Sign Out</a>
</td>
<div>
<span style=""position:absolute;left:0;top:35px;width:100%;height:2px;background-color:#8fc3d6;""></span>
</div>
<td style=""background-color: transparent;"" align=""left"" width=""106"">
<a class=""TextMenuLink"" href=""javascript:goToService('UAM::UAMbase');"" id=""AccountLinkID"">Account</a>
</td>
<div>
<span style=""position:absolute;left:0;top:35px;width:100%;height:2px;background-color:#8fc3d6;"">
</span>
</div>
<td style=""background-color: transparent;"" align=""left"" width=""109"">
<a class=""TextMenuLink"" href=""javascript:goToService('settings::mainindex');"" id=""SettingsLinkID"">Settings</a>
</td>
<div><span style=""position:absolute;left:0;top:35px;width:100%;height:2px;background-color:#8fc3d6;"">
</span>
</div>
<td style=""background-color: transparent;"" align=""left"" width=""78"">
<a href='javascript:CallPaneHelp(PH_TOC);' id=""HelpLinkID"">
<table><tr>
<td valign=""middle"" class=""TextMenuLinkSimulation"">Help</td>
<td valign=""middle"" width=""30"" height=""20"">
<span id=""helpIcon"" style='src:url(msntv:/Shared/Images/Icon_Help_RelatedLink.png);'></span>
</td>
</tr>
</table>
</a>
</td>
<div>
<span style=""position:absolute;left:0;top:35px;width:100%;height:2px;background-color:#8fc3d6;"">
</span>
</div>
</tr>
</table>
</div>
</div>
<div class=""infoPaneDiv"">
<div class=""promoImgDiv"">
<div style=""position:absolute; left:0px; top:0px"">
<img id=""PromoImageID"" width=""178"" height=""135"" border=""0"" hspace=""0"" alt=""Promotional Image"" src=""/Pages/Home/ads/webtv3.gif"">
</div>
<div style=""position:absolute; left:5px; top:0px""><a id=""PromoImageLinkID"" href="""">
<table width=""173"" height=""135""><tr><td></td></tr></table></a>
</div>
</div>
<div id=""timerRotatorDiv"" class=""personalPanelDiv"" onclick=""ClickRotator()"">
<div>
<div style=""top:0px; left:0px; width:176px; height:105px;"" xmlns:msntvuxp=""msntvuxp.microsoft.com"">
<div class=""PNGImage"" style=""width:176px;height:105px;src:/images/Home/HomeRotatorBGWeather.png;""></div>
</div>
<div style=""position:absolute; top:0px; left:0px; width:178px; height:107px;"" xmlns:msntvuxp=""msntvuxp.microsoft.com"">
<table class=""wthrTbl"" border=""0"" cellpadding=""1"" cellspacing=""0"">
<tr>
<td height=""4"" width=""4"" rowspan=""4""><img src=""images/Shared/s.gif"" height=""4"" width=""4""></td>
<td height=""4"" width=""45""><img src=""images/Shared/s.gif"" height=""4"" width=""45""></td>
<td height=""4"" width=""10""><img src=""images/Shared/s.gif"" height=""4"" width=""10""></td>
<td height=""4"" width=""65""><img src=""images/Shared/s.gif"" height=""4"" width=""65""></td>
</tr>
<tr>
<td id=""CityCellID"" class=""wthrCityCell"" colspan=""3"" valign=""top"">
<span class=""wthrCityText"">{@Weather.City}</span>
</td>
</tr>
<tr>
<td id=""TRCID"" class=""wthrTempCond"">
<table>
<tr>
<td id=""TemperatureCellID"" class=""wthrTempCell"">
<span class=""wthrTempTxt"">{@Weather.Temp}°F</span>
</td>
</tr>
<tr>
<td id=""ConditionCellID"" class=""wthrCondCell"">
<span class=""wthrCondTxt"">{Weather?.Description}</span>
</td>
</tr>
</table>
</td>
<td id=""PaddingID"" width=""10""><img src=""images/Shared/s.gif"" height=""1"" width=""10""></td>
<td id=""ConditionIconID"" class=""wthrCondIcon"">
<img src=""{@Weather?.Icon}"" alt=""Weather icon"" style=""width:65px;height:61px;"">
</td>
</tr>
<tr>
<td id=""ProviderID"" class=""wthrProvider"" colspan=""3"">
Open-Meteo
</td>
</tr>
</table>
</div>
<script xmlns:msntvuxp=""msntvuxp.microsoft.com"">
function clickPageRotatePanel() {{
location.href = ""/Pages/weather/yourcity"";
}}
</script>
</div>
</div>
<script>
InitRotator(timerRotatorDiv, 5000, new Array(""/home/MoneyModule.aspx"",""/Pages/weather/WeatherModule""));
</script>
<div ID=""clockID"" class=""clockDiv"">
</div>
<script>clockID.innerHTML = formClockLink();</script>
<div class=""newsHdlnDiv"">
<div class=""newsHdlnTitleDiv""><span class=""newsHdlnTitleText"">Today on TV2</span></div><table style=""top:0px;left:0px;width:365px;height:78px;""><tr><td><a class=""newsHdlnLink"" id=""newsHdlnLinkID1"" href=""{@NewsLink1}"">{NewsTitle1}</a></td></tr><tr><td><a class=""newsHdlnLink"" id=""newsHdlnLinkID2"" href=""{@NewsLink2}"">{NewsTitle2}</a></td></tr><tr><td><a class=""newsHdlnLink"" id=""newsHdlnLinkID3"" href=""{@NewsLink3}"">{NewsTitle3}</a></td></tr></table></td></tr></table><div class=""moreNewsDiv""><table><tr><td><img src=""msntv:/Shared/images/BulletCustom.gif"" height=""14"" width=""7""></td><td><img src=""images/Shared/s.gif"" height=""1"" width=""4""></td><td style=""height:37px""><a id=""moreNewsLinkID"" class=""moreNewsLink"" href=""../pages/TopStories"">More MSN news</a></td></tr></table></div>
</div>
<div class=""promoPanelDiv"">
<div style=""position:absolute; width:355px; height:70px;"">
<table style=""width:365px;""><td class=""promoHdlnTitle"" style=""color:#1D704C"">Using MSN TV</td><tr>
<td class=""promoHdlnCell""><a class=""promoHdlnLink"" id=""promoHdlnLinkID1"" href=""http://msntv.msn.com/pages/usingmsntv/tippage.aspx?id=set15"">Tip: Turn on audible dialing </a></td></tr><tr>
<td class=""promoHdlnCell""><a class=""promoHdlnLink"" id=""promoHdlnLinkID2"" href=""http://msntv.msn.com/pages/UsingMSNTV/Article.aspx?id=feature3"">Get better printing results</a></td></tr>
</table>
</div>
<div style=""position:absolute; left:161px; top:74px; width:200px;"">
<table class=""MoreUsingLinkTable""><tr><td><img src=""msntv:/Shared/Images/BulletCustom.gif"" height=""14"" width=""7""></td><td><img src=""/Images/Shared/s.gif"" height=""1"" width=""4""></td><td>
<a id=""MoreUsingLinkID"" class=""MoreNewsLink"" href=""http://headwaiter.trusted.msntv.msn.com/Pages/UsingMSNTV/Main"">Go to Using MSN TV</a>
</td></tr>
</table>
</div>
</div>
</div>
<div id=""searchID"" class=""searchDiv"">
<div class=""searchCenterDiv"">
<script>
function doSearch(country){{
if (searchFormID.searchFieldID.value == """")
{{
window.location = window.external.SafeGetServiceURL('search::search'); + ""?FORM=WEBTV&cfg=MSTVXML&v=1&c=""+country+""&x=26&y=14"";
}}
else
{{
window.location = window.external.SafeGetServiceURL('search::search') + ""?FORM=WEBTV&cfg=MSTVXML&v=1&c=""+country+""&x=26&y=14&q="" + escape(searchFormID.searchFieldID.value);
}}
}}
</script>
<div style=""position:absolute; z-index:1; left:0; top:0px; width:100%; height:2px; background-color:#0c7faa;"">
<table style="" width:100%; height:2px;"">
<tr>
<td height=""2"">
</td>
</tr>
</table>
</div>
<table class=""searchTbl"">
<form id=""searchFormID"" action=""javascript:doSearch('US')"">
<tr>
<td width=""10"">
</td><td>
<a class=""searchLink"" href=""
javascript:doSearch('US');
"">Search</a><span class=""searchLabelText""> or type www</span>
</td><td></td><td><span class=""searchFieldText""><input id=""searchFieldID"" name=""searchFieldName"" class=""searchField"" type=""text"" size=""28""></span></td><td></td>
<td valign=""center""><msntv:custombutton id=""GoButtonID"" onClick=""doSearch('US');"" label=""Go"" /></td></tr></form></table><div style=""position:absolute; left:0; top:33px; width:100%; height:2px; background-color:#1c4373;""><table style="" width:100%; height:2px;""><tr><td height=""2""></td></tr></table></div>
</div>
</div>
<div id=""iconNavBarID"" class=""iconNavBar"">
<table class=""iconNavBarMasterTbl"">
<tr>
<td align=""center"" valign=""middle"">
<table class=""iconNavBarTbl"">
<tr>
<td class=""iconNavBarTblFrameCell"">
<table class=""ApolloIcons"" xmlns:msntv=""http://tv.msn.com"">
<tr height=""70"">
<td>
<msntv:loopingDIV id=""navbar"" hasInitialFocus=""true"" divWidthPX=""554"" onLoopingDivReady=""handleLoopingDivReady()"" />
</td>
</tr>
<script>
var nIcons;
var ImgURL = new Array();
var ImgWidth = new Array();
var ImgOverURL = new Array();
var URL = new Array();
function goToFavorites() {{
window.open("" "", ""favoritespanel"", ""msntv:panel"");
}}
function goToMessenger() {{
if (window.external.SafeGetServiceURL('messenger::root') != null && window.external.SafeGetServiceURL('messenger::root') != """") window.open("" "", ""impanel"", ""msntv:panel"");
else
window.location = ""msntv:/OLTK/IMBlock.html"";
}}
function goToMail() {{
if (window.external.SafeGetServiceURL('mail::listmail') != null && window.external.SafeGetServiceURL('mail::listmail') != """") window.location = window.external.SafeGetServiceURL('mail::listmail');
else
window.location = ""msntv:/OLTK/EmailBlock.html"";
}}
function goToChat() {{
if (window.external.SafeGetServiceURL('chat::home') != null && window.external.SafeGetServiceURL('chat::home') != """") window.location = window.external.SafeGetServiceURL('chat::home');
else
window.location = ""msntv:/OLTK/chatBlock.html"";
}}
function goToSearch() {{
window.location = window.external.SafeGetServiceURL('search::main');
}}
function goToMaps() {{
window.location = window.external.SafeGetServiceURL('maps::main');
}}
function goToMusicHome() {{
window.location = window.external.SafeGetServiceURL('Music::Home');
}}
function goToVideoHome() {{
window.location = window.external.SafeGetServiceURL('Video::Home');
}}
function goToNetwork() {{
window.location = window.external.SafeGetServiceURL('Settings::HomeNetwork');
}}
function goToAccount() {{
window.location = window.external.SafeGetServiceURL('UAM::UAMbase');
}}
function goToSettings() {{
window.location = window.external.SafeGetServiceURL('settings::mainindex');
}}
function goToCenter(URL) {{
window.location = URL;
}}
function goToPhotosApp() {{
window.location = window.external.SafeGetServiceURL('Photos');
}}
function goToPhotosHome() {{
window.location = window.external.SafeGetServiceURL('Photo::Home');
}}
function initIcons() {{
for (index = 0; index < nIcons; index++) {{
var realIndex = (index + nIcons - 1) % nIcons;
var cellHTML = ""<span"" + "" onFocus=\""ImgObjs"" + realIndex + "".src='"" + ImgOverURL[realIndex] + ""'\"""" + "" onBlur=\""ImgObjs"" + realIndex + "".src='"" + ImgURL[realIndex] + ""'\"""";
cellHTML += "" onClick=\"""" + URL[realIndex] + ""\"""";
cellHTML += "">"" + ""<img"" + "" id='ImgObjs"" + realIndex + ""'"" + "" src='"" + ImgURL[realIndex] + ""' width="" + ImgWidth[realIndex] + "" height=61 border=0>"" + ""</span>"";
navbar.AddCellHTML(cellHTML, ImgWidth[realIndex]);
}}
}}
ImgURL[0] = ""/Images/Home/HomeIconFav.jpg"";
ImgOverURL[0] = ""/Images/Home/HomeIconFavOver.jpg"";
ImgWidth[0] = 87;
URL[0] = ""javascript:goToFavorites();"";
ImgURL[1] = ""/Images/Home/HomeIconMail.jpg"";
ImgOverURL[1] = ""/Images/Home/HomeIconMailOver.jpg"";
ImgWidth[1] = 70;
URL[1] = ""javascript:goToMail();"";
ImgURL[2] = ""/Images/Home/HomeIconMsgr.jpg"";
ImgOverURL[2] = ""/Images/Home/HomeIconMsgrOver.jpg"";
ImgWidth[2] = 99;
URL[2] = ""javascript:goToMessenger();"";
ImgURL[3] = ""/Images/Home/HomeIconMaps.jpg"";
ImgOverURL[3] = ""/Images/Home/HomeIconMapsOver.jpg"";
ImgWidth[3] = 60;
URL[3] = ""javascript:goToMaps();"";
ImgURL[4] = ""/Images/Home/HomeIconPhoto.jpg"";
ImgOverURL[4] = ""/Images/Home/HomeIconPhotoOver.jpg"";
ImgWidth[4] = 70;
URL[4] = ""javascript:goToPhotosApp();"";
ImgURL[5] = ""/Images/Home/HomeIconVideos.jpg"";
ImgOverURL[5] = ""/Images/Home/HomeIconVideosOver.jpg"";
ImgWidth[5] = 70;
URL[5] = ""javascript:goToVideoHome();"";
ImgURL[6] = ""/Images/Home/HomeIconMusic.jpg"";
ImgOverURL[6] = ""/Images/Home/HomeIconMusicOver.jpg"";
ImgWidth[6] = 66;
URL[6] = ""javascript:goToMusicHome();"";
ImgURL[7] = ""/Images/Home/HomeIconDiscuss.jpg"";
ImgOverURL[7] = ""/Images/Home/HomeIconDiscussOver.jpg"";
ImgWidth[7] = 72;
URL[7] = ""javascript:goToCenter('/Pages/Discuss/Home.aspx');"";
ImgURL[8] = ""/Images/Home/HomeIconMSNBC.jpg"";
ImgOverURL[8] = ""/Images/Home/HomeIconMSNBCOver.jpg"";
ImgWidth[8] = 68;
URL[8] = ""javascript:goToCenter('/Pages/News/TopStories.aspx');"";
ImgURL[9] = ""/Images/Home/HomeIconTWC.jpg"";
ImgOverURL[9] = ""/Images/Home/HomeIconTWCOver.jpg"";
ImgWidth[9] = 81;
URL[9] = ""javascript:goToCenter('/Pages/Weather/MyCity.aspx');"";
ImgURL[10] = ""/Images/Home/HomeIconEnt.jpg"";
ImgOverURL[10] = ""/Images/Home/HomeIconEntOver.jpg"";
ImgWidth[10] = 125;
URL[10] = ""javascript:goToCenter('/Pages/Entertainment/Home.aspx');"";
ImgURL[11] = ""/Images/Home/HomeIconTVListings.jpg"";
ImgOverURL[11] = ""/Images/Home/HomeIconTVListingsOver.jpg"";
ImgWidth[11] = 96;
URL[11] = ""javascript:goToCenter('http://tv.msn.com/tv/guide');"";
ImgURL[12] = ""/Images/Home/HomeIconSports.jpg"";
ImgOverURL[12] = ""/Images/Home/HomeIconSportsOver.jpg"";
ImgWidth[12] = 82;
URL[12] = ""javascript:goToCenter('/Pages/Sports/Main.aspx');"";
ImgURL[13] = ""/Images/Home/HomeIconMoney.jpg"";
ImgOverURL[13] = ""/Images/Home/HomeIconMoneyOver.jpg"";
ImgWidth[13] = 68;
URL[13] = ""javascript:goToCenter('/Pages/Money/Home.aspx');"";
ImgURL[14] = ""/Images/Home/HomeIconShopping.jpg"";
ImgOverURL[14] = ""/Images/Home/HomeIconShoppingOver.jpg"";
ImgWidth[14] = 62;
URL[14] = ""javascript:goToCenter('/Pages/Shopping/Main.aspx');"";
ImgURL[15] = ""/Images/Home/HomeIconGames.jpg"";
ImgOverURL[15] = ""/Images/Home/HomeIconGamesOver.jpg"";
ImgWidth[15] = 70;
URL[15] = ""javascript:goToCenter('/Pages/Games/Home.aspx');"";
ImgURL[16] = ""/Images/Home/HomeIconEncarta.jpg"";
ImgOverURL[16] = ""/Images/Home/HomeIconEncartaOver.jpg"";
ImgWidth[16] = 74;
URL[16] = ""javascript:goToCenter('http://g.msn.com/5TVANDURIL/1505')"";
ImgURL[17] = ""/Images/Home/HomeIconChat.jpg"";
ImgOverURL[17] = ""/Images/Home/HomeIconChatOver.jpg"";
ImgWidth[17] = 55;
URL[17] = ""javascript:goToChat();"";
ImgURL[18] = ""/Images/Home/HomeIconUsingMSN.jpg"";
ImgOverURL[18] = ""/Images/Home/HomeIconUsingMSNOver.jpg"";
ImgWidth[18] = 127;
URL[18] = ""javascript:goToCenter('/Pages/UsingMSNTV/Main');"";
ImgURL[19] = ""/Images/Home/HomeIconTTT.jpg"";
ImgOverURL[19] = ""/Images/Home/HomeIconTTTOver.jpg"";
ImgWidth[19] = 116;
URL[19] = ""javascript:goToCenter('/Pages/UsingMSNTV/ThingstoTry');"";
ImgURL[20] = ""/Images/Home/HomeIconSearch.jpg"";
ImgOverURL[20] = ""/Images/Home/HomeIconSearchOver.jpg"";
ImgWidth[20] = 84;
URL[20] = ""javascript:goToSearch();"";
ImgObjs = new Array();
ImgOverObjs = new Array();
nIcons = ImgURL.length;
ImgOverObjs[0] = new Image();
ImgOverObjs[0].src = ImgOverURL[0];
for (var i = 0; i < nIcons; i++) {{
ImgObjs[i] = new Image();
ImgObjs[i].src = ImgURL[i];
}}
for (var i = 1; i < nIcons; i++) {{
ImgOverObjs[i] = new Image();
ImgOverObjs[i].src = ImgOverURL[i];
}}
initIcons();
</script>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</div>
<script>
function callCGif()
{{
var i = new Image();
i.src = ""http://c.msn.com/c.gif?DI=1455&PI=68206&PS=45577&TP=http://msntv.msn.com/HomePage.htm&RF="";
}}
window.attachEvent(""onload"", callCGif);
</script>
</div>
</body></html>
");
@((MarkupString)htmlContent)
}
else
{
<PageTitle>Home</PageTitle>
<h1>Hello, world!</h1>
<footer>
<div style="position:fixed; left: 0px; bottom: 20px; width: 100vw;">
<TV2PCHomeNavBar />
</div>
<div style="position:fixed; left: 0px; bottom: 0; width: 100vw; height: 40px;">
<table cellpadding="0" cellspacing="0" width="100%" height="100%">
<tr>
<td width="560">
<img src="/Images/TV2PC/StatusBarBG.jpg" style="width: 100vw; height: 40px; object-fit: fill;"/>
<img src="/Images/TV2PC/Logo_MSNTVStatusBar.png" alt="Overlay Logo" class="overlay-logo" style="position: absolute; right: 0.1vw; bottom: 5px; max-width: 150%; z-index: 1001;" />
</td>
</tr>
</table>
</div>
</footer>
}
@code {
string userAgent { get; set; }
bool IsTV2 = false;
// News
string NewsLink1 = "http://sg1.trusted.msntv.msn.com/Pages/Tricks/he.mp3";
string NewsLink2 = "http://sg1.trusted.msntv.msn.com/Pages/Tricks/pokemon-black-2.mp3";
string NewsLink3 = "http://sg1.trusted.msntv.msn.com/Pages/Tricks/he.mp3";
string NewsTitle1 = "Ryder Smells";
string NewsTitle2 = "Ryder Smells";
string NewsTitle3 = "Ryder Smells";
// Weather mock
WeatherData Weather = new WeatherData
{
City = "San Jose",
Temp = "72",
Description = "Sunny",
Icon = "/Pages/Home/Weather/26.gif"
};
// Date data
DateData date = new DateData();
protected override void OnInitialized()
{
var request = httpContextAccessor.HttpContext?.Request;
IsTV2 = request != null && SecurityHandler.IsMsnTvClient(request) && SecurityHandler.IsTrustedDomain(request);
}
class WeatherData
{
public string City { get; set; }
public string Temp { get; set; }
public string Description { get; set; }
public string Icon { get; set; }
}
class DateData
{
public string date { get; }
public DateData()
{
date = DateTime.Now.ToString("dddd, MMMM d");
}
}
}

View File

@@ -0,0 +1,127 @@
const minisrv_service_file = true;
headers = `200 OK
Content-type: text/html`;
data = `<html>
<head>
<script>
var forceReload = false;
var l = 'd:' + new Date().valueOf() + '|';
function setCookie(name, value) {
var now = new Date();
var expires = new Date(now.getFullYear() + 1, now.getMonth(), now.getDate());
document.cookie = escape(name) + '=' + escape(value) + ';expires=' + expires.toGMTString() + ';path=/';
}
function getCookie(name) {
var str = document.cookie;
var arr = str.split('; ');
for (var i = arr.length - 1; i >= 0; i--) {
var c = arr[i].split('=');
if (c.length != 2) continue;
if (unescape(c[0]) == name) return unescape(c[1]);
}
return null;
}
function syncCookie(cookieName, propValue) {
var c = getCookie(cookieName);
l += 'g:' + cookieName + ':' + c + '|';
if (c != propValue) {
setCookie(cookieName, propValue);
l += 's:' + cookieName + ':' + propValue + '|';
var check = getCookie(cookieName);
if (check == propValue) forceReload = true;
}
}
var d = new Date();
var utcOffset = d.getTimezoneOffset();
syncCookie('UserUtcOffset', utcOffset);
var connSpeed;
try {
connSpeed = window.external.ClientCaps.connectionType;
} catch (e) {
connSpeed = "undetected";
}
syncCookie('UserConnectionSpeed', connSpeed);
try {
top.log(l);
} catch (e) {}
if (forceReload) location.replace(location.href);
</script>
</head>
<body>
<div style="top:0px; left:0px; width:176px; height:105px;">
<div class="PNGImage" style="width:176px;height:105px;src:/Images/Home/HomeRotatorBGStock.png;"></div>
</div>
<table cellpadding="0" cellspacing="0" class="stocksTbl">
<tbody>
<tr height="8">
<td width="7"></td>
<td width="75"></td>
<td width="5"></td>
<td width="14"></td>
<td width="7"></td>
<td width="65"></td>
<td width="5"></td>
</tr>
<tr>
<td></td>
<td class="stocksCell" style="font-weight:bold; overflow:hidden; text-overflow:ellipsis">Dow</td>
<td></td>
<td>
<div class="PNGImage" style="src:/Images/Home/HomeStocksUpArrow.png; width:14px; height:24px"></div>
</td>
<td></td>
<td class="stocksCell" style="text-align: right;">+54.11</td>
<td></td>
</tr>
<tr>
<td class="stocksRule" colspan="7"></td>
</tr>
<tr>
<td></td>
<td class="stocksCell" style="font-weight:bold; overflow:hidden; text-overflow:ellipsis">Nasdaq</td>
<td></td>
<td>
<div class="PNGImage" style="src:/Images/Home/HomeStocksUpArrow.png; width:14px; height:24px"></div>
</td>
<td></td>
<td class="stocksCell" style="text-align: right;">+6.31</td>
<td></td>
</tr>
<tr>
<td class="stocksRule" colspan="7"></td>
</tr>
<tr>
<td></td>
<td class="stocksCell" style="font-weight:bold; overflow:hidden; text-overflow:ellipsis">S&amp;P</td>
<td></td>
<td>
<div class="PNGImage" style="src:/Images/Home/HomeStocksUpArrow.png; width:14px; height:24px"></div>
</td>
<td></td>
<td class="stocksCell" style="text-align: right;">+3.19</td>
<td></td>
</tr>
<tr>
<td class="stocksRule" colspan="7"></td>
</tr>
<tr>
<td id="ProviderID" class="wthrProvider" colspan="6">Source: MSN Money</td>
</tr>
</tbody>
</table>
<!--<ROTATOR_FEEDBACK></ROTATOR_FEEDBACK>--><!--<ROTATOR_CLICKTHROUGH>/Pages/Money/MyStocks.aspx</ROTATOR_CLICKTHROUGH>-->
<script>
function clickPageRotatePanel() {
location.href = "/Pages/Money/MyStocks.aspx";
}
</script>
</body>
</html>`;

View File

@@ -0,0 +1,164 @@
@page "/Pages/Home/MoneyModule.aspx"
@using MSNTV2MainServer.Components.Layout
@using Microsoft.AspNetCore.Http
@using Microsoft.AspNetCore.Components;
@inject IHttpContextAccessor httpContextAccessor
<!-- Nulled layout as it is defined manually-->
@layout EmptyLayout
@if (IsTV2)
{
MarkupString htmlContent = new MarkupString($@"
<html>
<head>
<script>
var forceReload = false;
var l = 'd:' + new Date().valueOf() + '|';
function setCookie(name, value) {{
var now = new Date();
var expires = new Date(now.getFullYear() + 1, now.getMonth(), now.getDate());
document.cookie = escape(name) + '=' + escape(value) + ';expires=' + expires.toGMTString() + ';path=/';
}}
function getCookie(name) {{
var str = document.cookie;
var arr = str.split('; ');
for (var i = arr.length - 1; i >= 0; i--) {{
var c = arr[i].split('=');
if (c.length != 2) continue;
if (unescape(c[0]) == name) return unescape(c[1]);
}}
return null;
}}
function syncCookie(cookieName, propValue) {{
var c = getCookie(cookieName);
l += 'g:' + cookieName + ':' + c + '|';
if (c != propValue) {{
setCookie(cookieName, propValue);
l += 's:' + cookieName + ':' + propValue + '|';
var check = getCookie(cookieName);
if (check == propValue) forceReload = true;
}}
}}
var d = new Date();
var utcOffset = d.getTimezoneOffset();
syncCookie('UserUtcOffset', utcOffset);
var connSpeed;
try {{
connSpeed = window.external.ClientCaps.connectionType;
}} catch (e) {{
connSpeed = ""undetected"";
}}
syncCookie('UserConnectionSpeed', connSpeed);
try {{
top.log(l);
}} catch (e) {{}}
if (forceReload) location.replace(location.href);
</script>
</head>
<body>
<div style=""top:0px; left:0px; width:176px; height:105px;"">
<div class=""PNGImage"" style=""width:176px;height:105px;src:/Images/Home/HomeRotatorBGStock.png;""></div>
</div>
<table cellpadding=""0"" cellspacing=""0"" class=""stocksTbl"">
<tbody>
<tr height=""8"">
<td width=""7""></td>
<td width=""75""></td>
<td width=""5""></td>
<td width=""14""></td>
<td width=""7""></td>
<td width=""65""></td>
<td width=""5""></td>
</tr>
<tr>
<td></td>
<td class=""stocksCell"" style=""font-weight:bold; overflow:hidden; text-overflow:ellipsis"">Dow</td>
<td></td>
<td>
<div class=""PNGImage"" style=""src:/Images/Home/HomeStocksUpArrow.png; width:14px; height:24px""></div>
</td>
<td></td>
<td class=""stocksCell"" style=""text-align: right;"">+54.11</td>
<td></td>
</tr>
<tr>
<td class=""stocksRule"" colspan=""7""></td>
</tr>
<tr>
<td></td>
<td class=""stocksCell"" style=""font-weight:bold; overflow:hidden; text-overflow:ellipsis"">Nasdaq</td>
<td></td>
<td>
<div class=""PNGImage"" style=""src:/Images/Home/HomeStocksUpArrow.png; width:14px; height:24px""></div>
</td>
<td></td>
<td class=""stocksCell"" style=""text-align: right;"">+6.31</td>
<td></td>
</tr>
<tr>
<td class=""stocksRule"" colspan=""7""></td>
</tr>
<tr>
<td></td>
<td class=""stocksCell"" style=""font-weight:bold; overflow:hidden; text-overflow:ellipsis"">S&amp;P</td>
<td></td>
<td>
<div class=""PNGImage"" style=""src:/Images/Home/HomeStocksUpArrow.png; width:14px; height:24px""></div>
</td>
<td></td>
<td class=""stocksCell"" style=""text-align: right;"">+3.19</td>
<td></td>
</tr>
<tr>
<td class=""stocksRule"" colspan=""7""></td>
</tr>
<tr>
<td id=""ProviderID"" class=""wthrProvider"" colspan=""6"">Source: MSN Money</td>
</tr>
</tbody>
</table>
<!--<ROTATOR_FEEDBACK></ROTATOR_FEEDBACK>--><!--<ROTATOR_CLICKTHROUGH>/Pages/Money/MyStocks.aspx</ROTATOR_CLICKTHROUGH>-->
<script>
function clickPageRotatePanel() {{
location.href = ""/Pages/Money/MyStocks.aspx"";
}}
</script>
</body>
</html>
");
@((MarkupString)htmlContent)
}
else
{
<PageTitle>Home</PageTitle>
}
@code
{
string userAgent { get; set; }
bool IsTV2 = false;
protected override void OnInitialized()
{
userAgent = httpContextAccessor.HttpContext.Request.Headers["User-Agent"];
//Check if the client is an MSNTV2 box. If it is, we should return the TV2 page and not the Blazor based Page.
if(userAgent.Contains("MSNTV"))
{
IsTV2 = true;
StateHasChanged();
}
}
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,104 @@
const minisrv_service_file = true;
headers = `200 OK
Content-type: text/html`;
data = `<html>
<head>
<script>
var forceReload = false;
var l = 'd:' + new Date().valueOf() + '|';
function setCookie(name, value) {
var now = new Date();
var expires = new Date(now.getFullYear() + 1, now.getMonth(), now.getDate());
document.cookie = escape(name) + '=' + escape(value) + ';expires=' + expires.toGMTString() + ';path=/';
}
function getCookie(name) {
var str = document.cookie;
var arr = str.split('; ');
for (var i = arr.length - 1; i >= 0; i--) {
var c = arr[i].split('=');
if (c.length != 2) continue;
if (unescape(c[0]) == name) return unescape(c[1]);
}
return null;
}
function syncCookie(cookieName, propValue) {
var c = getCookie(cookieName);
l += 'g:' + cookieName + ':' + c + '|';
if (c != propValue) {
setCookie(cookieName, propValue);
l += 's:' + cookieName + ':' + propValue + '|';
var check = getCookie(cookieName);
if (check == propValue) forceReload = true;
}
}
var d = new Date();
var utcOffset = d.getTimezoneOffset();
syncCookie('UserUtcOffset', utcOffset);
var connSpeed;
try {
connSpeed = window.external.ClientCaps.connectionType;
} catch (e) {
connSpeed = "undetected";
}
syncCookie('UserConnectionSpeed', connSpeed);
try {
top.log(l);
} catch (e) {}
if (forceReload) location.replace(location.href);
</script>
</head>
<body>
<div style="top:0px; left:0px; width:176px; height:105px;" xmlns:msntvuxp="msntvuxp.microsoft.com">
<div class="PNGImage" style="width:176px;height:105px;src:/Images/Home/HomeRotatorBGWeather.png;"></div>
</div>
<div style="position:absolute; top:0px; left:0px; width:178px; height:107px;" xmlns:msntvuxp="msntvuxp.microsoft.com">
<table class="wthrTbl" border="0" cellpadding="1" cellspacing="0">
<tbody>
<tr>
<td height="4" width="4" rowspan="4"><img src="/Images/Shared/s.gif" height="4" width="4"></td>
<td height="4" width="45"><img src="/Images/Shared/s.gif" height="4" width="45"></td>
<td height="4" width="10"><img src="/Images/Shared/s.gif" height="4" width="10"></td>
<td height="4" width="65"><img src="/Images/Shared/s.gif" height="4" width="65"></td>
<td height="4" width="10" rowspan="4"><img src="/Images/Shared/s.gif" height="4" width="10"></td>
</tr>
<tr>
<td id="CityCellID" class="wthrCityCell" colspan="3" valign="top"><span class="wthrCityText">Your City</span></td>
</tr>
<tr>
<td id="TRCID" class="wthrTempCond">
<table>
<tbody>
<tr>
<td id="TemperatureCellID" class="wthrTempCell"><span class="wthrTempTxt">63&#176;/50</span></td>
</tr>
<tr>
<td id="ConditionCellID" class="wthrCondCell"><span class="wthrCondTxt">Mostly</span><br><span class="wthrCondTxt"> Cloudy</span></td>
</tr>
</tbody>
</table>
</td>
<td id="PaddingID" width="10"><img src="/Images/Shared/s.gif" height="1" width="10"></td>
<td id="ConditionIconID" class="wthrCondIcon"><span class="PNGImage" style="src:/Images/Shared/Weather/28.png;width:65px;height:61px;"></span></td>
</tr>
<tr>
<td id="ProviderID" class="wthrProvider" colspan="3">The Weather Channel &#174;</td>
</tr>
</tbody>
</table>
</div>
<!--<ROTATOR_FEEDBACK></ROTATOR_FEEDBACK>--><!--<ROTATOR_CLICKTHROUGH>/Pages/Weather/YourCity.aspx</ROTATOR_CLICKTHROUGH>-->
<script xmlns:msntvuxp="msntvuxp.microsoft.com">
function clickPageRotatePanel() {
location.href = "/Pages/Weather/YourCity.aspx";
}
</script>
</body>
</html>`;

View File

@@ -0,0 +1,166 @@
@page "/Pages/Home/WeatherModule.aspx"
@using MSNTV2MainServer.Components.Layout
@using Microsoft.AspNetCore.Http
@using Microsoft.AspNetCore.Components;
@inject IHttpContextAccessor httpContextAccessor
<!-- Nulled layout as it is defined manually-->
@layout EmptyLayout
@if (IsTV2)
{
MarkupString htmlContent = new MarkupString($@"
<html>
<head>
<script>
var forceReload = false;
var l = 'd:' + new Date().valueOf() + '|';
function setCookie(name, value) {{
var now = new Date();
var expires = new Date(now.getFullYear() + 1, now.getMonth(), now.getDate());
document.cookie = escape(name) + '=' + escape(value) + ';expires=' + expires.toGMTString() + ';path=/';
}}
function getCookie(name) {{
var str = document.cookie;
var arr = str.split('; ');
for (var i = arr.length - 1; i >= 0; i--) {{
var c = arr[i].split('=');
if (c.length != 2) continue;
if (unescape(c[0]) == name) return unescape(c[1]);
}}
return null;
}}
function syncCookie(cookieName, propValue) {{
var c = getCookie(cookieName);
l += 'g:' + cookieName + ':' + c + '|';
if (c != propValue) {{
setCookie(cookieName, propValue);
l += 's:' + cookieName + ':' + propValue + '|';
var check = getCookie(cookieName);
if (check == propValue) forceReload = true;
}}
}}
var d = new Date();
var utcOffset = d.getTimezoneOffset();
syncCookie('UserUtcOffset', utcOffset);
var connSpeed;
try {{
connSpeed = window.external.ClientCaps.connectionType;
}} catch (e) {{
connSpeed = ""undetected"";
}}
syncCookie('UserConnectionSpeed', connSpeed);
try {{
top.log(l);
}} catch (e) {{}}
if (forceReload) location.replace(location.href);
</script>
</head>
<body>
<div style=""top:0px; left:0px; width:176px; height:105px;"" xmlns:msntvuxp=""msntvuxp.microsoft.com"">
<div class=""PNGImage"" style=""width:176px;height:105px;src:/Images/Home/HomeRotatorBGWeather.png;""></div>
</div>
<div style=""position:absolute; top:0px; left:0px; width:178px; height:107px;"" xmlns:msntvuxp=""msntvuxp.microsoft.com"">
<table class=""wthrTbl"" border=""0"" cellpadding=""1"" cellspacing=""0"">
<tbody>
<tr>
<td height=""4"" width=""4"" rowspan=""4""><img src=""/Images/Shared/s.gif"" height=""4"" width=""4""></td>
<td height=""4"" width=""45""><img src=""/Images/Shared/s.gif"" height=""4"" width=""45""></td>
<td height=""4"" width=""10""><img src=""/Images/Shared/s.gif"" height=""4"" width=""10""></td>
<td height=""4"" width=""65""><img src=""/Images/Shared/s.gif"" height=""4"" width=""65""></td>
<td height=""4"" width=""10"" rowspan=""4""><img src=""/Images/Shared/s.gif"" height=""4"" width=""10""></td>
</tr>
<tr>
<td id=""CityCellID"" class=""wthrCityCell"" colspan=""3"" valign=""top""><span class=""wthrCityText"">Your City</span></td>
</tr>
<tr>
<td id=""TRCID"" class=""wthrTempCond"">
<table>
<tbody>
<tr>
<td id=""TemperatureCellID"" class=""wthrTempCell""><span class=""wthrTempTxt"">63°/50</span></td>
</tr>
<tr>
<td id=""ConditionCellID"" class=""wthrCondCell""><span class=""wthrCondTxt"">Mostly</span><br><span class=""wthrCondTxt""> Cloudy</span></td>
</tr>
</tbody>
</table>
</td>
<td id=""PaddingID"" width=""10""><img src=""/Images/Shared/s.gif"" height=""1"" width=""10""></td>
<td id=""ConditionIconID"" class=""wthrCondIcon""><span class=""PNGImage"" style=""src:/Images/Shared/Weather/28.png;width:65px;height:61px;""></span></td>
</tr>
<tr>
<td id=""ProviderID"" class=""wthrProvider"" colspan=""3"">The Weather Channel ®</td>
</tr>
</tbody>
</table>
</div>
<!--<ROTATOR_FEEDBACK></ROTATOR_FEEDBACK>--><!--<ROTATOR_CLICKTHROUGH>/Pages/Weather/YourCity.aspx</ROTATOR_CLICKTHROUGH>-->
<script xmlns:msntvuxp=""msntvuxp.microsoft.com"">
function clickPageRotatePanel() {{
location.href = ""/Pages/Weather/YourCity.aspx"";
}}
</script>
</body>
</html>
");
@((MarkupString)htmlContent)
}
else
{
<PageTitle>Home</PageTitle>
<h1>Hello, world!</h1>
<footer>
<div style="position:fixed; left: 0px; bottom: 20px; width: 100vw;">
<TV2PCHomeNavBar />
</div>
<div style="position:fixed; left: 0px; bottom: 0; width: 100vw; height: 40px;">
<table cellpadding="0" cellspacing="0" width="100%" height="100%">
<tr>
<td width="560">
<img src="/Images/TV2PC/StatusBarBG.jpg" style=" width: 100vw; height: 40px; object-fit: fill;"/>
<img src="/Images/TV2PC/Logo_MSNTVStatusBar.png" alt="Overlay Logo" class="overlay-logo" style="position: absolute; right: 0.1vw; bottom: 5px; max-width: 150%; z-index: 1001;" />
</td>
</tr>
</table>
</div>
</footer>
}
@code
{
string userAgent { get; set; }
bool IsTV2 = false;
string NewsLink1 = "https://google.com";
string NewsLink2 = "https://yahoo.com";
string NewsLink3 = "https://bing.com";
string NewsTitle1 = "Google reigns superior over the universe";
string NewsTitle2 = "Who even uses Yahoo anymore?";
string NewsTitle3 = "Oh god, it's Bing! (Now with extra piss)";
protected override void OnInitialized()
{
userAgent = httpContextAccessor.HttpContext.Request.Headers["User-Agent"];
//Check if the client is an MSNTV2 box. If it is, we should return the TV2 page and not the Blazor based Page.
@if(userAgent.Contains("MSNTV"))
{
IsTV2 = true;
StateHasChanged();
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 671 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 811 B

Some files were not shown because too many files have changed in this diff Show More