Compare commits

...

19 Commits

Author SHA1 Message Date
Ryder
8089bd4439 Remove redundant files 2026-05-08 00:35:58 +01:00
zefie
750435fc83 small cleanup 2026-05-05 14:39:39 -04:00
zefie
fd67132da9 store the msntv2 token so we can look it up later 2026-05-05 14:35:37 -04:00
zefie
43a87347b8 some more msntv2 updates 2026-05-05 09:20:31 -04:00
zefie
e1d2c59ed5 some updates 2026-05-05 07:43:40 -04:00
2d64acaab6 Merge pull request 'Fix TV2 support' (#1) from feature/FixTv2 into dev
Reviewed-on: #1
2026-05-05 02:20:34 +02:00
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
71 changed files with 3790 additions and 1553 deletions

View File

@@ -2,7 +2,7 @@
## user_config.json ## user_config.json
`user_config.json` (in the project root) is where you put your local configuration overrides. It merges on top of `includes/config.json`**do not edit `includes/config.json` directly**. `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: 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:
@@ -35,17 +35,19 @@ node tools/configurator.js <dot.path.key> --delete [--overwrite]
## Setting service_ip ## 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 MAME/Viewer on the same machine as minisrv. `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.bind_ip 192.168.1.x --overwrite node tools/configurator.js config.service_ip 192.168.1.x --overwrite
``` ```
--- ---
## Setting user_data_key ## 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 break existing accounts. `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 node tools/configurator.js config.keys.user_data_key YOUR_RANDOM_SECRET --overwrite
@@ -55,3 +57,31 @@ To generate a random key:
``` ```
openssl rand -base64 32 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

@@ -228,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 // Deprecation warnings configuration
const deprecationWarnings = { const deprecationWarnings = {
// Array of deprecated patterns with their details // Array of deprecated patterns with their details
patterns: [ patterns: loadDepreciatedPatterns(),
{
// 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"
}
],
// Enable/disable deprecation warnings globally // Enable/disable deprecation warnings globally
enabled: true, enabled: true,
@@ -363,7 +382,7 @@ const runScriptInVM = function (script_data, user_contextObj = {}, privileged =
"service_vaults": service_vaults, "service_vaults": service_vaults,
"service_deps": service_deps, "service_deps": service_deps,
"ssid_sessions": ssid_sessions, "ssid_sessions": ssid_sessions,
"moveArrayKey": wtvshared.moveArrayKey, "moveArrayKey": wtvshared.moveArrayKey, // deprecated - use wtvshared.moveArrayKey() instead
"cwd": (filename) ? path.dirname(filename) : __dirname, // current working directory "cwd": (filename) ? path.dirname(filename) : __dirname, // current working directory
// Our prototype overrides // Our prototype overrides
@@ -2685,9 +2704,11 @@ if (protocolHandledPorts.size > 0) console.log(` * Started ${protocolHandledPort
const listening_ip_string = (minisrv_config.config.bind_ip !== "0.0.0.0") ? "IP: " + minisrv_config.config.bind_ip : "all interfaces"; 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") { 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(" * 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(" * 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, you can run tools/update_user_data_key.js to update existing accounts with the new key."); 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."); 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,29 @@
-----BEGIN CERTIFICATE-----
MIIE8TCCA9mgAwIBAgIQhTOF71uduRa0SXk4z+A7ujANBgkqhkiG9w0BAQUFADB0
MRkwFwYDVQQDDBBtaW5pc3J2IHNlcnZpY2VzMREwDwYDVQQIDAhOZXcgWW9yazEL
MAkGA1UEBhMCVVMxHjAcBgkqhkiG9w0BCQEWD3plZmllQHplZmllLm5ldDEXMBUG
A1UECgwOWmVmaWUgTmV0d29ya3MwIBcNMDAwMTAxMTIwMDAwWhgPMjA5OTEyMzEy
MzU5NTlaMFExKTAnBgNVBAMTIGhlYWR3YWl0ZXIudHJ1c3RlZC5tc250di5tc24u
Y29tMRcwFQYDVQQKEw5aZWZpZSBOZXR3b3JrczELMAkGA1UEBhMCVVMwggEiMA0G
CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6pNNH2lF7SFx8cEIF1ImA7AI4bv/W
qvbErvUYJOfrOLXOfvXnxWEbEfDk9+XEf+JD8PQo2rvze1cVcXjVO7i2m+c4jdWw
X/VPdRM0NpoppFXbWC7nWNuXhZD/S7f6pUEJez7BYUpEeBFdR9eFb8VPo8+kefMz
inYznvP1UAn9wwoSIFDglX9QbijkJ/ZKtOY3vxCMVBZedWVnMPEJt928NJBNDGcC
VeV1thEAAVbQBf5nyhF9VfblTzEHoxq+d6rvi4rVkd0ZYqQPCcafDFccXf6YNQcz
8cmwzha61bgLbJLPNPiSqbqL8GNfsHbt2vyX6OhYpKwF+Y2CCp0xbGflAgMBAAGj
ggGeMIIBmjAJBgNVHRMEAjAAMAsGA1UdDwQEAwIFoDATBgNVHSUEDDAKBggrBgEF
BQcDATCCAWkGA1UdEQSCAWAwggFcgiBoZWFkd2FpdGVyLnRydXN0ZWQubXNudHYu
bXNuLmNvbYIZc2cxLnRydXN0ZWQubXNudHYubXNuLmNvbYIZc2cyLnRydXN0ZWQu
bXNudHYubXNuLmNvbYIZc2czLnRydXN0ZWQubXNudHYubXNuLmNvbYIZc2c0LnRy
dXN0ZWQubXNudHYubXNuLmNvbYINbXNudHYubXNuLmNvbYIWbWFpbC5zZXJ2aWNl
cy5saXZlLmNvbYIObG9naW4ubGl2ZS5jb22CEXBvcHRpbWl6ZS5tc24uY29tghFm
YXZvcml0ZXMubXNuLmNvbYIRbWVzc2VuZ2VyLm1zbi5jb22CEWxpdmVmaWxlc3Rv
cmUuY29tghZ1c2Vycy5zdG9yYWdlLmxpdmUuY29tgglnLm1zbi5jb22CF21zbmlh
bG9naW4ucGFzc3BvcnQuY29tgg1taW5pc3J2LmxvY2FsMA0GCSqGSIb3DQEBBQUA
A4IBAQAZTy82heE64hCFxEiIFIxglGyPVU14wA2gXrv82mci/U0h/xBHfIfQWY8d
ULM6dO6kEk2DriBbo2ET10rkBwCTqa1iSDRN1eg0umdT2vbEYigjOelZJQqJi3Ua
LGBrPh8PK7juGa37aEdMWFLxmDtfEXE//OmMiliXU6bIi44pqM571X3Q3WPh3C3K
xOCOwQMgTPovLJDwRIJNyTrnb0kI+1s7oOtZ+QUQa7frY0Bgxn4IMEnZoIkOcAkh
R1m/OKyjjqQ8EVM73dTeiNr0yByG64C8dsVhJVXPT3GZOl7p5Pof9VfQg9Qr39Vh
ds7T/BVzQ79G8IXQ+AgZnZHu7pnn
-----END CERTIFICATE-----

View File

@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAuqTTR9pRe0hcfHBCBdSJgOwCOG7/1qr2xK71GCTn6zi1zn71
58VhGxHw5PflxH/iQ/D0KNq783tXFXF41Tu4tpvnOI3VsF/1T3UTNDaaKaRV21gu
51jbl4WQ/0u3+qVBCXs+wWFKRHgRXUfXhW/FT6PPpHnzM4p2M57z9VAJ/cMKEiBQ
4JV/UG4o5Cf2SrTmN78QjFQWXnVlZzDxCbfdvDSQTQxnAlXldbYRAAFW0AX+Z8oR
fVX25U8xB6Mavneq74uK1ZHdGWKkDwnGnwxXHF3+mDUHM/HJsM4WutW4C2ySzzT4
kqm6i/BjX7B27dr8l+joWKSsBfmNggqdMWxn5QIDAQABAoIBAEGq0DNNmrF3aiLW
FESc3KwhXT6hvx22FRBqRg1ynq5hy4WVocsj5OBzVYAZwBt8qw0gb6cYHlyyHpeK
zuqnEnwdKiL5tB9UA6krFdCfDWptSU/dHNOEre4HrlZEO7zR+6nsVM4Q/uJMJD/f
kPJ/urokdl/2EB0oMCJFYKwEtx8yrxJriNAjr4he0ibLHKiSTobanpbJDaDrIvOk
3njH8TNxjj/wdIaLJIWP/xPNTgMmUERYT6fDRe9p7gXg8R2+kSuvhpZSCa/fHxp0
6E1UESZyfHQgieUfgD7SB9Teq2gxTBIHGDsRhjgHLAa5R+p0lc8DEIgO+32hOI6v
p3CdThkCgYEA90EtOQZeGt9yVfLOTP7G6WiuiBC/kS4aEJDbr+JqtFJtEOXZWWuh
pbT7M+r/IDS/+TFPRtXe4Xm8eFmUIyW59V+9/jAqhU43zQZye159oKs6lyTmk84j
byQVsarnYTQxa2psqVWDbzfDAR3M01vIPa9pSmeCBoZVCxoi0Cd8OtMCgYEAwT7V
s9K0oajY49Kgeo8RA77/a1tGqlaEuQRX8KR85wcGm3nG7rDMaxbEoGurhwy8n4HW
KigQxFezhjXaTvFonCgTg5Dm0jAaCtHsJw48tGpkXZWrJ+elCZZPSred3Z1hKmvJ
SJ64dGP+cS4icw5NzsoGEpJZHrr9BBVYbDNML2cCgYEAmXi8QEQij12Y056VzRbr
kp+mjdCPh+bsyNGRezf38ZukFTQGWEnFmVyf/BbmazAy5NNlmNtRr/TnNnCr0bEu
Hw9hl/B/xCTL4BgbYVZCdkMyZ/TApofyWJ82VAR4AE7sSfdSIT1yCsu63+uGYr76
qMdDfKqI+9HP4cdESp3nr38CgYAGKOOU9M1vLbukH22gGnlXXjo0CNfKzDE02I+Z
CxU0JAQw5oPRze7mJvajimsQRfapOvFBrL9EEuuVBphr1cQY3iopEnBZGNFrsN9P
K2QB+DY0yXWIMxkOoizq28l7a+3R9VeYKf8FLr7IisjsU/Nk+QmSg/m1Qg6Yl7mW
0VfHVwKBgHT1oKjsQQc+a1dxwgXO1AKTMpUbpy88vRMaGu2emnHcTg7Y0lpwhsMo
4SSh2kN4ijLs8BeDbpxQf4ygWrqxWyeV73c4om/ADo4RWzMBzMm3lEcHiHA8jJSJ
cFCTew4Xiqzkbae5zU+mL6Dsxw8KSrkzSa62P8dgAGWQ1RsjoI3F
-----END RSA PRIVATE KEY-----

View File

@@ -1,29 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIFCzCCA/OgAwIBAgIQdCYWD0et5QHOJ3OYuF0r2TANBgkqhkiG9w0BAQUFADBw
MQswCQYDVQQGEwJVUzELMAkGA1UECAwCT0gxEzARBgNVBAcMCkJ1dHQgQ3JhY2sx
IDAeBgNVBAoMF1VuZGVyd2VhciBJbnNwZWN0b3IgIzEyMR0wGwYDVQQLDBRUaGlu
ZyBMb29rZXIgRXhwZXJ0czAeFw0yNjA1MDEwMjMzMDhaFw00MTA0MjgwMjMzMDha
MFExKTAnBgNVBAMTIGhlYWR3YWl0ZXIudHJ1c3RlZC5tc250di5tc24uY29tMRcw
FQYDVQQKEw5aZWZpZSBOZXR3b3JrczELMAkGA1UEBhMCVVMwggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQCpSe+Vpv9qwb5aDgMNfWRTeXAGvTzhP+olLX+J
2WczAXr4FUSLE4LPyD43se26u4GBvGmKD9512/GZKCtMbKPmfBdIqeq/CF2gx8xh
e55qF8OuOdxMukOLXsTmvf4slwp3/N6gyze/PMmX+ku/gbotwPL0sv/9Vf1+PVTY
6Fje2EU0ra6xJADeL9gazdl6QBxiJ+py+49SiZMS8N4MICOfklykENmjDoM211W6
mIRgRZebxijNiZNFeWeXzjxzAAWi701TDs8ksNHSRBG2pajDZ+XgB8D1T+yXWbPz
zylePg6HlG8n+asd43wakF8aER26eCT5hyCb4+SkkRClRXLHAgMBAAGjggG+MIIB
ujAJBgNVHRMEAjAAMAsGA1UdDwQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATCC
AYkGA1UdEQSCAYAwggF8giBoZWFkd2FpdGVyLnRydXN0ZWQubXNudHYubXNuLmNv
bYIZc2cxLnRydXN0ZWQubXNudHYubXNuLmNvbYIZc2cyLnRydXN0ZWQubXNudHYu
bXNuLmNvbYIZc2czLnRydXN0ZWQubXNudHYubXNuLmNvbYIZc2c0LnRydXN0ZWQu
bXNudHYubXNuLmNvbYINbXNudHYubXNuLmNvbYIWbWFpbC5zZXJ2aWNlcy5saXZl
LmNvbYIec3luYy1zZzEudHJ1c3RlZC5tc250di5tc24uY29tgg5sb2dpbi5saXZl
LmNvbYIRcG9wdGltaXplLm1zbi5jb22CEWZhdm9yaXRlcy5tc24uY29tghFtZXNz
ZW5nZXIubXNuLmNvbYIRbGl2ZWZpbGVzdG9yZS5jb22CFnVzZXJzLnN0b3JhZ2Uu
bGl2ZS5jb22CCWcubXNuLmNvbYIXbXNuaWFsb2dpbi5wYXNzcG9ydC5jb22CDW1p
bmlzcnYubG9jYWwwDQYJKoZIhvcNAQEFBQADggEBAD9O6j8bWtsX9OGf0kT3u1dy
n6F+MQWX+vI4C9131Nso7cf7/+FyPcg17ewKw1MJ33ZpzCqhupAnN1lZPikGnl+t
VacegsqI2mX1ycD11s1EleobHLc28uEQHDd79Dwn6fA2/EOijyqsILJHB6kzLjH6
DV/sapv4JtNMlKDjfHDhtiI2jtpYTfkoZqjs7WsNmaJBcJ/NgTtl3hFSMiN/MLQ0
O9wyrvNheINIJ01trpcgLDpmwCG0lYoa8AOdRZccl0KR7IsdVBcV1ANFguepQXI5
dc/VJcFWsYs0puGdhPPZHgiZV4pzmfU+rCM/AoNxDdRBrVSal6Um5YyhlmFtobA=
-----END CERTIFICATE-----

View File

@@ -1,27 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAqUnvlab/asG+Wg4DDX1kU3lwBr084T/qJS1/idlnMwF6+BVE
ixOCz8g+N7HturuBgbxpig/eddvxmSgrTGyj5nwXSKnqvwhdoMfMYXueahfDrjnc
TLpDi17E5r3+LJcKd/zeoMs3vzzJl/pLv4G6LcDy9LL//VX9fj1U2OhY3thFNK2u
sSQA3i/YGs3ZekAcYifqcvuPUomTEvDeDCAjn5JcpBDZow6DNtdVupiEYEWXm8Yo
zYmTRXlnl848cwAFou9NUw7PJLDR0kQRtqWow2fl4AfA9U/sl1mz888pXj4Oh5Rv
J/mrHeN8GpBfGhEdungk+Ycgm+PkpJEQpUVyxwIDAQABAoIBAAeA8Rr4Irs/wtqU
17BXv20LyGXG+74z3oKu/iTFKfyYnAUfDaouqB+z0z4tG+yo/B3XIK0BC8sY3NpD
EKPkkfi19B0qD0xnUON5LA//j0l4lI3dtrHpaSJ9nRCvatuANa2sOPzqIiZDVYLB
Y8qCkP+yLqn71HSY81dUl4Zgsznr7w0VGXLzJd3nUKYL/FRVpsBPy7uHsUemB8YO
mgGIVg7l7SpVX/tzZFnoQyPeiABv3hHTWoquxQfT9fqGQEm66cmzQP8yefaVvw1l
Eiikx/QWX2SsXfqXdMmiQUI/y+1WV2XL955e9BFzlH6HrePaj9CIrP3+HLtw1fMZ
It20WqECgYEA6FWcM2auqqVWrbee8oaoUT/XlQH8TZndlbWGSsFcQZptVTyB+ZMf
DyDBAAh18ix7XVEtm6+2pioBZtou5Vp/RjWM1OI3f0mvVgVi2zFIIy52WrWxTHiv
OSZpAv3NZEoiq/K3InrF3RolVklfQFv9TJBLmfNfTVtuEd47eBiLZckCgYEAuohW
n0pPQhnBb1OiDv82GfyUVORVX4zcjvFGnTbqHiyH1l3+KNBtWQ2Wy5TsBifhXk5h
TbGydO4RePeCaUBf4QFtABFsGH+et7Ci5ALvZBTLbCouvZRcDINnKsF0Wd1ZP9TW
F7ToN6q3vkhbHIpY59Il96Ije7F6CwV7SQR0nA8CgYBcgzpffVOvv4Z0RdmU2OnM
8I73VoMQo2QIaO/AdJ43wTYn6qAWsO59J52yVawhcnTtA5YVmDIymCdWvSpPSWnE
my4o1qsilEStDBgBD+6Zk7atCAxBVwzuxMyr1EQk2yBTN6KUqC6BjBex9CVpizeh
dROlibM5Kl753nPvrlZTgQKBgQC0fi6LbgUpafChv6RlrI/2L1B8oID3tz7IVjFE
+RkrX12FkWfYqG3WqO6MSark/fv2HBPNcS/EM4TWr3ESVUcxWwbU9QbK4dp71kCY
LzrjdbetD1gw+3jiZtgSKCVku2mb+V+8isHU861eQ3deM4R5tQAmEU8SZpY4SfKU
oeoQAwKBgDhaHQinuYCa1w9Mzy6/AagSmS9yhXX1zR4OSgvF2lU0hT18CAR6LgOR
gHOc6OscxDxheS38dHSIyRqD7F5hu1mO0KsGSCdmWIEKMkge8/yjrg4E4CPB5ICZ
RcKZRL/rSQZIsdeVJM9i5FiDUaJUX+cvLJQAC1XLfRrBxCHsY0E1
-----END RSA PRIVATE KEY-----

View File

@@ -1,17 +0,0 @@
headwaiter.trusted.msntv.msn.com
sg1.trusted.msntv.msn.com
sg2.trusted.msntv.msn.com
sg3.trusted.msntv.msn.com
sg4.trusted.msntv.msn.com
msntv.msn.com
mail.services.live.com
sync-sg1.trusted.msntv.msn.com
login.live.com
poptimize.msn.com
favorites.msn.com
messenger.msn.com
livefilestore.com
users.storage.live.com
g.msn.com
msnialogin.passport.com
minisrv.local

View File

@@ -1,5 +0,0 @@
-----BEGIN DH PARAMETERS-----
MIGHAoGBAOjeZEDvMxiY+T4AMUIJ6jPFhflzUwO6EPBc0+Fn3C13WGQgsx9N3Rjg
bZsF4Sbqs62+KFTYb5/1PVPSOxyif0CJLRC8VhvCl5CZ2DsS6nJ3sstPxtfhQdn+
X1kbvqAbHlvNtE6w5ketHv3gK6y4d9qdVnwicZW3uV1sJ2dg4RfDAgEC
-----END DH PARAMETERS-----

View File

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

View File

@@ -1,307 +0,0 @@
const minisrv_service_file = true;
let BoxId = request_headers.query.BoxId;
if (Array.isArray(BoxId)) BoxId = BoxId[0];
let clientIp = socket.remoteAddress;
let banned = false;
let sessionId = null;
// Use the shared MSNTV2 helper injected by WTV-MSNTV2 VM context.
if (BoxId) {
if (!BoxId || BoxId.length != 20 || !/^\d+$/.test(BoxId))
{
console.warn("Invalid BoxId format "+BoxId+" from "+clientIp);
banned = true;
} else {
sessionId = encodeSessionID(BoxId);
}
} else if (request_headers.cookie && request_headers.cookie.SessionID) {
BoxID = decodeSessionID(request_headers.cookie.SessionID);
sessionId = request_headers.cookie.SessionID;
} else {
console.warn("No BoxId provided by client "+clientIp);
banned = true;
}
if (!sessionId && !banned) {
banned = true;
}
if (!session_data && BoxId) {
console.log("Missing session_data for BoxId %s", BoxId);
}
let registered = false;
let username = '';
if (session_data) {
registered = session_data.isRegistered();
if (registered) {
username = session_data.getSessionData("subscriber_username") || '';
}
}
// Current UTC time
const now = new Date();
// Time data object
const timeData = {
hh: now.getUTCHours(),
mm: now.getUTCMinutes(),
ss: now.getUTCSeconds(),
mo: now.getUTCMonth() + 1, // JS months are 0-based
dd: now.getUTCDate(),
yyyy: now.getUTCFullYear()
};
// Timezone mapping (C# tuple → JS array or object)
const timezoneMap = {
"UTC": {
standardName: "UTC",
standardOffset: 0,
daylightName: "UTC",
daylightOffset: 0
}
};
// Destructure like the C# tuple deconstruction
const {
standardName,
standardOffset,
daylightName,
daylightOffset
} = timezoneMap["UTC"];
// Set session cookie on the client
if (sessionId) {
setCookie('SessionID', sessionId, { path: '/' });
}
headers = `200 OK
Content-type: text/html`
data = `<html>
<head>
<title id="title"></title>
</head>
<body>
<iframe id="checkmail" style="display:none"></iframe>
<script src="msntv:/Javascript/TVShell.js" language="javascript"></script>
<script src="msntv:/Javascript/ServiceList.js" language="javascript"></script>
<script src="msntv:/Javascript/GuestUser.js" language="javascript"></script>
<script language="javascript">
try {
var TVShell = new ActiveXObject("MSNTV.TVShell");
var sink = new ActiveXObject("MSNTV.MultipleEventSink");
function getIDCRLCode(value) {
return value & 0xFFFF;
}
function isIDCRLErrorCode(value) {
return (value >>> 16) != 0;
}
var email = TVShell.UserManager.EMail;
var wanProvider = TVShell.ConnectionManager.WANProvider;
var banned = ${banned}; // JavaScript boolean value
var registered = ${registered}; // JavaScript boolean value
var username = "${username}"; // JavaScript string value
InitializeGuestMode();
RemoveGuestUsers();
if (!banned) {
TVShell.AddSecretCode(10000); // sync shit
TVShell.AddSecretCode(10001); // sync shit
TVShell.AddSecretCode(10002); // sync shit
TVShell.AddSecretCode(93288); // Service Select
TVShell.AddSecretCode(77437); // Spooky Options
TVShell.AddSecretCode(6145539); // Force Crash
var entry = TVShell.ServiceList.Add("connection::login");
entry.URL = "https://headwaiter.trusted.msntv.msn.com/connection/login.aspx?BoxId=${BoxId}";
TVShell.ServiceList.Save();
}
function CheckForUser(usernameToCheck) {
var UserManager = TVShell.UserManager;
for (var i=0; i<UserManager.Count; i++) {
var user = UserManager.Item(i);
if (user == usernameToCheck) {
return true;
}
}
return false;
}
function DoLogin() {
var currentUser = TVShell.UserManager.CurrentUser;
if (currentUser == null) {
if (banned === true) {
var url = 'https://sg1.trusted.msntv.msn.com/connection/banned.html';
var myPanel = TVShell.PanelManager.Item('main');
if (myPanel) myPanel.GotoURL(url);
} else {
if (registered === true) {
if (!CheckForUser(username)) {
var user = TVShell.UserManager.AddNew(username);
if (user) {
user.IsPersistent = true;
}
}
}
SetProgress('Welcome, New User!', 100);
var myPanel = TVShell.PanelManager.Item('main')
if (registered === true) {
var signon = TVShell.BuiltinServiceList.Item("SignOn");
var panel = TVShell.PanelManager.FocusedPanel;
var atLogin = false;
if ( signon && panel && panel.Name == "main" )
{
if ( IsMainPanelOnPage( signon.URL ) ) atLogin = true;
}
if (!atLogin) {
myPanel.ClearTravelLog();
myPanel.NoBackToMe = true;
GotoSignOn();
}
} else {
if (myPanel) myPanel.GotoURL('https://sg1.trusted.msntv.msn.com/register/Establish-your-MSN-TV-Account.html');
}
if (myPanel) {
myPanel.ClearTravelLog();
myPanel.NoBackToMe = true;
}
}
} else {
if (banned === true) {
var url = 'https://sg1.trusted.msntv.msn.com/connection/banned.html';
var myPanel = TVShell.PanelManager.Item('service');
if (myPanel) myPanel.GotoURL(url);
}
}
if (currentUser != null) {
var serviceArgs = new Array();
var ProductionArgs = new Array("msntv.msn.com", "MBI", 0, 0,
"mail.services.live.com", "MBI", 0, 0,
"livefilestore.com", "MBI", 0, 0,
"messenger.msn.com", "?id=507", 0, 0,
"spaces.live.com", "MBI", 0, 0
);
var PPEArgs = new Array();
var INTArgs = new Array();
serviceArgs[0] = ProductionArgs;
serviceArgs[1] = PPEArgs;
serviceArgs[2] = INTArgs;
try {
TVShell.LoginManager.IDCRLInitialize(0);
TVShell.LoginManager.IDCRLLogonAndAuthToServices(serviceArgs[0]);
} catch (e) {
if (window.console) console.log("IDCRL error: " + e.message);
}
GoToUserCheck();
}
}
function DoPoptimization() {
if (wanProvider === "MSNIANB") {
var connector = GetConnectorByName("LocalPOP");
if (connector == null) {
connector = TVShell.ConnectionManager.MSNIAManager.Connectors.Add("modem");
connector.AreaCode = "";
connector.Exchange = "";
connector.DialingFlags = 0x00001000;
connector.Name = "LocalPOP";
connector.LocationName = "LocalPOP";
TVShell.ConnectionManager.Save();
connector.Poptimize("0", connector.AreaCode, connector.Exchange);
}
if (connector.Phonebook == null || connector.Phonebook.length === 0) {
if (connector.AreaCode && connector.Exchange) {
connector.Poptimize(connector.AreaCode, connector.Exchange);
}
}
}
}
function GetConnectorByName(name) {
var connectors = TVShell.ConnectionManager.MSNIAManager.Connectors;
for (var i = 0; i < connectors.length; i++) {
if (connectors[i].Name === name) {
return connectors[i];
}
}
return null;
}
function CheckBoxID() {
SetProgress("${minisrv_config.config.service_name} [${minisrv_config.config.hide_minisrv_version ? "beta" : minisrv_version_string.replace("zefie's wtv minisrv ","")}] Welcome, ${username != '' ? username : 'Guest'}!", 20);
}
function GoToUserCheck() {
if (banned === true) {
var url = 'https://headwaiter.trusted.msntv.msn.com/connection/banned.html';
var myPanel = TVShell.PanelManager.Item('service');
if (myPanel) myPanel.GotoURL(url);
} else if (registered) {
GotoSignOn();
}
}
function SetProgress(text, percent) {
if (progressPanel) {
progressPanel.Document.SetProgressText(text);
progressPanel.Document.SetProgressPercent(percent);
}
}
var progressPanel = TVShell.PanelManager.Item('progress');
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 {
CheckBoxID();
DoPoptimization();
DoLogin();
try {
TVShell.DeviceControl.SetTimeZone(${standardOffset}, "${standardName}", 0, "");
} catch (e) {
if (window.console) console.log("SetTimeZone error: " + e.message);
}
try {
TVShell.DeviceControl.SetClock(${timeData.hh}, ${timeData.mm}, ${timeData.ss}, ${timeData.mo}, ${timeData.dd}, ${timeData.yyyy});
TVShell.DeviceControl.ClockSet = true;
} catch (e) {
if (window.console) console.log("SetClock error: " + e.message);
}
}
} catch (e) {
if (window.console) console.log("Error in boxcheck: " + e.message);
var myPanel = TVShell ? TVShell.PanelManager.Item('main') : null;
if (myPanel) myPanel.GotoURL('https://headwaiter.trusted.msntv.msn.com/connection/error.html');
}
</script>
</body>
</html>`;

View File

@@ -27,7 +27,7 @@ data = `<HTML>
} }
} }
function GotoBoxCheck() { function GotoBoxCheck() {
var url = 'https://headwaiter.trusted.msntv.msn.com/connection/boxcheck.html'; var url = 'https://sg1.trusted.msntv.msn.com/connection/GatePage.aspx?phase=BoxCheck&purpose=Authorize';
var parms=''; var parms='';
parms += 'BoxId=' + tvShell.SystemInfo.BoxIDService + '&'; parms += 'BoxId=' + tvShell.SystemInfo.BoxIDService + '&';
parms += 'WANProvider=' + tvShell.ConnectionManager.WANProvider + '&'; parms += 'WANProvider=' + tvShell.ConnectionManager.WANProvider + '&';

View File

@@ -1,32 +0,0 @@
const minisrv_service_file = true;
// Todo: auth if not guest
headers = `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");
var UserManager = tvShell.UserManager;
var home = "https://sg1.trusted.msntv.msn.com/Home/Home.aspx";
tvShell.ConnectionManager.ServiceState = 'Authorized';
UserManager.SetCurrentUserIsAuthorized(true);
var currentUser = UserManager.CurrentUser;
if (currentUser != null) {
currentUser.IsAuthorized = true;
}
var myPanel = tvShell.PanelManager.Item('main');
if (myPanel) {
myPanel.ClearTravelLog();
myPanel.NoBackToMe = true;
myPanel.GotoURL(home);
}
</script>
</body>
</html>`;

View File

@@ -0,0 +1,470 @@
const minisrv_service_file = true;
if (!session_data) {
session_data = new WTVClientSessionData(minisrv_config, (socket.ssid || null))
}
// 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;
debug("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`;
}
debug(`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}">
${minisrv_config.config.service_name} [minisrv ${minisrv_config.config.hide_minisrv_version ? "beta" : minisrv_version_string.replace("zefie's wtv minisrv ","")}]
</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);
wtvshared.storeToken(token, socket.ssid, userId, expiresTime);
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);
wtvshared.storeToken(defaultToken, socket.ssid, userId, expiresTime);
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 {
debug("No post_data found. Available keys:", Object.keys(request_headers));
return generateErrorResponse("0x80048820", "No POST data received");
}
if (!requestBody || requestBody.trim() === '') {
debug("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')) {
debug("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;
debug(`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 {
debug("Token validation failed");
return generateErrorResponse("0x80048821", "Invalid token");
}
} else {
debug("No token found in CipherValue");
return generateErrorResponse("0x80048820", "Missing credentials/token");
}
}
else if (email && password) {
debug(`Extracted - Email: ${email}, Password: ${password ? '***' : 'empty'}`);
if (email && email.indexOf('@') < 0) {
const domain = minisrv_config.config.domain_name || 'minisrv.local';
email = `${email}@${domain}`;
}
userEmail = email;
firstName = email.split('@')[0];
userId = crypto.createHash('md5').update(email).digest('hex');
const validAuth = validateCredentials(email, password);
if (!validAuth) {
debug("Invalid credentials");
return generateErrorResponse("0x80048821", "Invalid credentials");
} else {
debug(`Authentication successful for: ${userEmail} (${userId})`);
}
}
else {
debug("Missing both credentials and token");
return generateErrorResponse("0x80048820", "Missing credentials/token");
}
if (!userId || !userEmail) {
debug("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}`);
}
}
function validateCredentials(email, password) {
username = email.split('@')[0];
result_ary = session_data.findAccountByUsername(username);
if (result_ary[0]) {
if (!socket.ssid) {
socket.ssid = result_ary[1];
// second arg should handle secondary users
session_data.setSSID(socket.ssid, result_ary[2]);
}
return session_data.validateUserPassword(password);
}
return false;
}
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);

View File

@@ -4,15 +4,15 @@ const minisrv_service_file = true;
const WeatherCity = 'Your City'; const WeatherCity = 'Your City';
const WeatherTemp = '72'; const WeatherTemp = '72';
const WeatherDescription = 'Sunny'; const WeatherDescription = 'Sunny';
const WeatherIcon = '/Pages/Home/Weather/26.gif'; const WeatherIcon = '/Home/Weather/26.gif';
// News headlines // News headlines
const NewsLink1 = 'http://sg1.trusted.msntv.msn.com/Pages/Tricks/he.mp3'; const NewsLink1 = '';
const NewsLink2 = 'http://sg1.trusted.msntv.msn.com/Pages/Tricks/pokemon-black-2.mp3'; const NewsLink2 = '';
const NewsLink3 = 'http://sg1.trusted.msntv.msn.com/Pages/Tricks/he.mp3'; const NewsLink3 = '';
const NewsTitle1 = 'Ryder Smells'; const NewsTitle1 = '...';
const NewsTitle2 = 'Ryder Smells'; const NewsTitle2 = '...';
const NewsTitle3 = 'Ryder Smells'; const NewsTitle3 = '...';
headers = `200 OK headers = `200 OK
Content-type: text/html`; Content-type: text/html`;
@@ -22,7 +22,7 @@ data = `<html xmlns:msntv>
<head><title>Home</title> <head><title>Home</title>
<?import namespace="msntv" implementation="HTC/Shared/CustomButton.htc"?> <?import namespace="msntv" implementation="HTC/Shared/CustomButton.htc"?>
<?import namespace="msntv" implementation="/Pages/Home/Shared/BaseClient/HTCTransforms/en-us/LoopingDIV.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> <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"> <link href="/Include/2.0.261.778/localhost-1700/Home/Anduril/CssTransforms/en-us/Home.css" type="text/css" rel="StyleSheet">
@@ -187,7 +187,7 @@ data = `<html xmlns:msntv>
<div class="promoImgDiv"> <div class="promoImgDiv">
<div style="position:absolute; left:0px; top:0px"> <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"> <img id="PromoImageID" width="178" height="135" border="0" hspace="0" alt="Promotional Image" src="/Home/ads/webtv3.gif">
</div> </div>
<div style="position:absolute; left:5px; top:0px"><a id="PromoImageLinkID" href=""> <div style="position:absolute; left:5px; top:0px"><a id="PromoImageLinkID" href="">
<table width="173" height="135"><tr><td></td></tr></table></a> <table width="173" height="135"><tr><td></td></tr></table></a>

View File

@@ -1,629 +0,0 @@
@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

@@ -1,164 +0,0 @@
@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

@@ -1,166 +0,0 @@
@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: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 B

View File

@@ -0,0 +1,502 @@
const minisrv_service_file = true;
const wtv_news_service_name = minisrv_config.services[service_name].usenet_service;
const wtvnews = new WTVNews(minisrv_config, wtv_news_service_name);
const service_config = minisrv_config.services[wtv_news_service_name];
if (service_config.local_nntp_port && wtvnewsserver) {
const tls_options = {
ca: this.wtvshared.getServiceDep('wtv-news/localserver_ca.pem'),
key: this.wtvshared.getServiceDep('wtv-news/localserver_key.pem'),
cert: this.wtvshared.getServiceDep('wtv-news/localserver_cert.pem'),
checkServerIdentity: () => { return null; }
}
if (wtvnewsserver.username)
wtvnews.initializeUsenet("127.0.0.1", service_config.local_nntp_port, tls_options, wtvnewsserver.username, wtvnewsserver.password);
else
wtvnews.initializeUsenet("127.0.0.1", service_config.local_nntp_port, tls_options);
} else {
if (service_config.upstream_auth)
wtvnews.initializeUsenet(service_config.upstream_address, service_config.upstream_port, service_config.upstream_tls || null, service_config.upstream_auth.username || null, service_config.upstream_auth.password || null);
else
wtvnews.initializeUsenet(service_config.upstream_address, service_config.upstream_port, service_config.upstream_tls || null);
}
async function throwError(e) {
console.log(e);
const errpage = wtvshared.doErrorPage(400 + " " + e.toString(), null, e.toString());
sendToClient(socket, errpage[0], errpage[1]);
}
function isToday (chkdate) {
const today = new Date()
return chkdate.getDate() === today.getDate() &&
chkdate.getMonth() === today.getMonth() &&
chkdate.getFullYear() === today.getFullYear()
}
async function WebTVListGroup(group) {
const page_limit_default = 100;
wtvnews.connectUsenet().then(() => {
wtvnews.selectGroup(group).then((response) => {
let limit_per_page = (request_headers.query.limit) ? parseInt(request_headers.query.limit) : page_limit_default;
const page = (request_headers.query.chunk) ? parseInt(request_headers.query.chunk) : 0;
let page_start = (limit_per_page * page) + 1;
let page_end = (page + 1) * limit_per_page;
if (page_end > response.group.high) {
page_end = response.group.high;
limit_per_page = (page_end - (limit_per_page / (page + 1))) + limit_per_page;
}
wtvnews.listGroup(group, page, limit_per_page).then((response) => {
if (response.code === 211) {
let NGCount = response.group.number;
const NGArticles = response.group.articleNumbers;
page_start = (limit_per_page * page) + 1;
page_end = (page + 1) * limit_per_page;
wtvnews.getHeaderObj(NGArticles).then((messages) => {
NGCount = NGArticles.length;
messages = wtvnews.sortByResponse(messages);
wtvnews.quitUsenet();
headers = `200 OK
Connection: Keep-Alive
Content-Type: text/html
wtv-expire: News.aspx?group=${request_headers.query.group}`
data = `<HTML>
<HEAD>
<script language=javascript>
if (top.frames.length > 1)
top.location="news:${request_headers.query.group}";
</script>
<TITLE>${request_headers.query.group}</TITLE>
</HEAD>
<body bgcolor="191919" text="42BD52" link="1bb0f1" vlink="826f7e">
<table cellspacing=0 cellpadding=0>
<td height=31 valign=top>
<font size="+1" color="E7CE4A">
<blackface>
<shadow>
Group: ${request_headers.query.group}
</shadow>
</blackface>
</font>
</table>
<font size=4>
`
if (NGCount === 0 || isNaN(NGCount)) {
data += `This group has no postings`;
} else {
data += NGCount + " posting";
if (NGCount !== 1)
data += "s"
}
data += `
</font>
<br>
<img src="wtv-home:/ROMCache/Spacer.gif" width=0 height=8>
`;
if (NGCount > 0) {
data += `
<td width=180 valign=bottom align=right>
<table cellspacing=0 cellpadding=0>
<td rowspan=4 height=26 width=30>
${(page > 0) ? `<a href="News.aspx?group=${group}&chunk=${page - 1}${(limit_per_page !== page_limit_default) ? `&limit=${limit_per_page}` : ''}"><img src="ListPrevious.gif"></a>` : `<img src="ListPrevious_D.gif">`}
<td rowspan=4 height=26 width=11>
<img src="ListLeftEdge.gif">
<td height=2 valign=top align=left bgcolor="2b2b2b">
<td rowspan=4 height=26 width=11>
<img src="ListRightEdge.gif">
<td rowspan=4 height=26 width=30>
${(page_end < NGCount) ? `<a href="News.aspx?group=${group}&chunk=${page + 1}${(limit_per_page !== page_limit_default) ? `&limit=${limit_per_page}` : ''}"><img src="ListNext.gif"></a>` : `<img src="ListNext_D.gif">`}
<td rowspan=4 width=5>
<tr>
<td height=2 valign=top align=left>
<img src="wtv-home:/ROMCache/Spacer.gif" width=1 height=1>
<tr>
<td height=20 valign=middle align=center>
${page_start}-${page_end}
<tr>
<td height=2 valign=top align=left bgcolor="000000">
<img src="wtv-home:/ROMCache/Spacer.gif" width=1 height=1>
<tr>
<td colspan=5 height=3>
</table> `;
}
data += `</table>
<TABLE width=446 cellspacing=0 cellpadding=0>
<tr>
<td rowspan=4>
<tr>
<img src="wtv-home:/ROMCache/Spacer.gif" width=10 height=1>
<td height=2 width=436 bgcolor="2B2B2B">
<img src="wtv-home:/ROMCache/Spacer.gif" width=436 height=1>
<tr>
<td height=1>
<tr>
<td height=2 bgcolor="0D0D0D">
<img src="wtv-home:/ROMCache/Spacer.gif" width=1 height=1>
<tr>
<td height=6>
</TABLE>`
if (NGCount > 0) {
Object.keys(messages).forEach(function (k) {
const message = messages[k].article;
const has_relation = (messages[k].relation !== null) ? true : false;
const date_obj = new Date(Date.parse(message.headers.DATE));
const date = (isToday(date_obj)) ? strftime("%I:%M %p", date_obj) : strftime("%b %d '%y", date_obj)
data += `
<table cellspacing=0 cellpadding=0>
<tr>
<td abswidth=10>
<td abswidth=426 height=42 valign=bottom>
<table cellspacing=0 cellpadding=0 nocolor selected>
<tr>
${(has_relation) ? `<td abswidth=20 rowspan=2 valign=top><font size="+2">&#149;` : ''}
<td abswidth=426 maxlines=1>
<a href="News.aspx?group=${request_headers.query.group}&article=${message.articleNumber}" id="${message.messageId}">
<font color=1bb0f1>${(message.headers.SUBJECT) ? wtvshared.htmlEntitize(message.headers.SUBJECT) : "(No Subject)"}
</a>
<tr>
<td maxlines=1>
<font size="-1" color=544f53><b>
${(message.headers.FROM.indexOf(' ') > 0) ? message.headers.FROM.split(' ')[0] : message.headers.FROM}, ${date}
</table>
<td abswidth=10>
</table>`;
});
}
data += `
<img src="wtv-home:/ROMCache/Spacer.gif" width=1 height=6><br>
<TABLE width=446 cellspacing=0 cellpadding=0>
<tr>
<td rowspan=4 width=10 height=1>
<img src="wtv-home:/ROMCache/Spacer.gif" width=436 height=1>
<tr>
<td height=1>
<tr>
<td height=2 bgcolor="0D0D0D">
<img src="wtv-home:/ROMCache/Spacer.gif" width=1 height=1>
<tr>
<td height=6>
</TABLE>
<table cellspacing=0 cellpadding=0>
<tr>
<td width=10 height=1 valign=top align=left>
<img src="wtv-home:/ROMCache/Spacer.gif" width=10 height=1>
<td height=33 width=256 valign=bottom>
</BODY>
</HTML>`;
sendToClient(socket, headers, data);
}).catch((e) => {
// getHeaderObj err
throwError(e)
});;
}
}).catch((e) => {
// listGroup error
throwError("No such group");
});
}).catch((e) => {
// selectGroup error
throwError("No such group")
});
}).catch((e) => {
// connect error
throwError(e)
});
}
async function WebTVShowMessage(group, article) {
const theArticle = parseInt(article);
wtvnews.connectUsenet().then(() => {
wtvnews.selectGroup(group).then((response) => {
wtvnews.getArticle(theArticle).then((response) => {
wtvnews.quitUsenet();
headers = `200 OK
Content-type: text/html`;
let signature = null;
let message_colors = session_data.mailstore.defaultColors;
const display_signature = true; // todo make a toggle
const message = wtvnews.parseAttachments(response);
const message_body = message.text;
let attachments = null;
let signature_index = null;
wtvnews.debug(message);
if (message.attachments) attachments = message.attachments;
if (attachments) {
if (Object.keys(attachments).length > 0) {
Object.keys(attachments).forEach((k) => {
if (attachments[k].filename === "wtv_signature.html" && attachments[k].content_type.match(/text\/html/)) {
signature = attachments[k].data;
signature_index = k;
return false;
}
});
if (signature_index) attachments.splice(signature_index, 1);
}
}
if (message_body.indexOf("<body")) {
const default_colors = session_data.mailstore.defaultColors;
message_colors = session_data.mailstore.getSignatureColors(message_body);
if ((message_colors === default_colors) && signature) message_colors = null;
}
if (!message_colors && signature) message_colors = session_data.mailstore.getSignatureColors(signature);
if (signature) message_colors = session_data.mailstore.getSignatureColors(signature);
data = `<head>
<sendpanel
action="/Post.aspx?message_forward_id=1&mailbox_name=inbox"
message="Forward this post to someone else."
label="Forward">
<title>
${(response.article.headers.SUBJECT) ? wtvshared.htmlEntitize(response.article.headers.SUBJECT) : '(No subject)'}
</title>
</head>
<print blackandwhite>
<body bgcolor=${message_colors.bgcolor}
text=${message_colors.text}
link=${message_colors.link}
vlink=${message_colors.vlink}
vspace=0
hspace=0>
<table cellspacing=0 cellpadding=0>
<tr>
<td abswidth=10 rowspan=99>
<td height=16>
<td rowspan=99>&nbsp;&nbsp;
<td>
<td abswidth=20 rowspan=99>
<tr>
<td colspan=3 height=39 valign=top>
<font color="E7CE4A" size=+1><blackface><shadow>
Post
<tr>
<td valign=top>
Group:
<td>
${wtvshared.htmlEntitize(response.article.headers.NEWSGROUPS)}
<tr>
<td valign=top>
Date: <td>
${strftime("%a, %b %e, %Y, %I:%M%P", new Date(Date.parse(response.article.headers.DATE)))}
<tr>
<td valign=top>
From:
<td>`;
if (message.from_name !== message.from_addr) {
data += `<a href="client:showalert?sound=none&message=Would%20you%20like%20to%20add%20%3Cblackface%3E${wtvshared.htmlEntitize(message.from_name)}%3C%2Fblackface%3E%20to%20your%20address%20list%3F&buttonlabel2=No&buttonaction2=client:donothing&buttonlabel1=Yes&buttonaction1=wtv-mail:/addressbook%3Faction%3Deditfromheader%26noresponse%3Dtrue%26nickname%3D${wtvshared.escape(wtvshared.escape(message.from_name))}%26address%3D${wtvshared.escape(wtvshared.escape(message.from_addr))}%26new_address%3Dtrue">${wtvshared.htmlEntitize(message.from_addr)} </a>`;
} else {
data += `${wtvshared.htmlEntitize(response.article.headers.FROM)}`;
}
data += `<tr>
<td nowrap valign=top>
<td>
<tr>
<td valign=top>Subject:
<td>${(response.article.headers.SUBJECT) ? wtvshared.htmlEntitize(response.article.headers.SUBJECT) : '(No subject)'}
</table>
<br><br>
<table cellspacing=0 cellpadding=0>
<tr>
<td abswidth=10 rowspan=99>
<tr>
<td>
`;
let allow_html = false;
let body_data = '';
let attachment_data = '';
let signature_data = '';
if (message_body) {
if (message_body.indexOf("<html>") >= 0) {
allow_html = true;
}
body_data += (allow_html) ? wtvshared.sanitizeSignature(message_body) : wtvshared.htmlEntitize(message_body, true)
body_data += `<br><br>`;
}
if (signature) signature_data += wtvshared.sanitizeSignature(signature);
if (attachments) {
if (attachments[0]) {
if (attachments[0].filename === "message.html") {
body_data += wtvshared.sanitizeSignature(attachments[0].data);
delete attachments[0];
}
}
const supported_images = /image\/(jpe?g|png|gif|x-wtv-bitmap)/;
const supported_audio = /audio\/(mp[eg|2|3]|midi?|wav|x-wav|mod|x-mod)/;
attachments.forEach((v, k) => {
if (v.content_type) {
if (v.content_type.match(supported_images))
attachment_data += `<img border=2 src="attachment.aspx?group=${group}&article=${article}&attachment_id=${k}&wtv-title=Video%20Snapshot"><br><br>`;
else if (v.content_type.match(supported_audio))
attachment_data += `<table href="attachment.aspx?group=${group}&article=${article}&attachment_id=${k}" width=386 cellspacing=0 cellpadding=0>
<td align=left valign=middle><img src="FileSound.gif" align=absmiddle><font color="#189CD6">&nbsp;&nbsp;${(v.filename) ? (v.filename) : "Audio file"} (${v.content_type.split('/')[1]} attachment)</font>
<td align=right valign=middle>
</table><br><br>`;
else if (v.content_type.match("text/html"))
attachment_data += wtvshared.sanitizeSignature(v.data);
else
attachment_data += `<table width=386><td><td align=left valign=middle><font color="#565656"><i>A file ${(v.filename) ? `(${v.filename}) ` : ''}that WebTV cannot use, with type ${v.content_type} is attached to this message.</i></font>`
}
});
}
/*
if (message.url) {
data += `Included Page: <a href="${(message.url)}">${wtvshared.htmlEntitize(message.url_title).replace(/&apos;/gi, "'")}`;
}
*/
data += body_data;
data += signature_data + "<p>";
data += attachment_data;
data += "</table></body></html>";
sendToClient(socket, headers, data);
}).catch((e) => {
// no such article
const post_unavailable_file = this.wtvshared.getServiceDep('wtv-news/post-unavailable.html');
console.log(e);
if (fs.existsSync(post_unavailable_file)) {
headers = "200 OK\nContent-type: text/html";
data = fs.readFileSync(post_unavailable_file).toString('ascii').replace("${group}", group).replace("${minisrv_config.config.service_logo}", minisrv_config.config.service_logo).replace("${message_colors.bgcolor}",session_data.mailstore.defaultColors.bgcolor);
sendToClient(socket, headers, data);
} else {
throwError(e);
}
});
}).catch((e) => {
// no such group
throwError(e);
});
}).catch((e) => {
//no connection
throwError(e);
});
}
function WebTVSearchGroups(search) {
wtvnews.connectUsenet().then(() => {
wtvnews.listGroups(search).then((response) => {
wtvnews.quitUsenet();
headers = `200 OK
Content-type: text/html
wtv-expire-all: News.aspx?search=`;
data = `<HTML>
<HEAD>
<DISPLAY fontsize=medium>
<TITLE>${(response.length === 0) ? "No " : ""}Discussion groups found</TITLE>
</HEAD>
<body
bgcolor="191919" text="42BD52" link="189CD6"
vlink="189CD6"
hspace=0
vspace=0>
<table cellspacing=0 cellpadding=0>
<tr>
<td abswidth=10>
<td colspan=3>
<table cellspacing=0 cellpadding=0>
<tr>
<td valign=center absheight=80>
<font size="+2" color="E7CE4A"><blackface><shadow>
${(response.length === 0) ? "No " : ""}Discussion groups found
</table>
<td abswidth=20>
<tr>
<td>
<td WIDTH=198 HEIGHT=200 VALIGN=top ALIGN=left>`;
if (response.length === 0) {
data += `There are no discussion groups that match your request. Do you want to look for something else?`;
} else {
response.forEach((group) => {
data += `<hr width=436>
<IMG src="wtv-home:/ROMCache/Spacer.gif" width=1 height=6><br>
<table cellspacing=0 cellpadding=0>
<tr>
<td width=10>
<td width=426> <table selected cellspacing=0 cellpadding=0>
<tr>
<td abswidth=401 height=19 valign=top>
<a href="News.aspx?group=${group.name}"><shadow><b>${group.name}</b></shadow></a>
<td width=10>
`
if (group.description) {
data += `<tr><td colspan=3 width=10 height=6><tr><td width=10><td colspan=99><i><font color=828282>${group.description}</font></i>`
}
data += "</table>";
});
}
data += `
</table>
<TABLE width=446 cellspacing=0 cellpadding=0>
<tr>
<td rowspan=3 width=10 height=1>
<img src="wtv-home:/ROMCache/Spacer.gif" width=10 height=1>
<td height=2 width=436 bgcolor="2B2B2B">
<img src="wtv-home:/ROMCache/Spacer.gif" width=436 height=1>
<tr>
<td height=1>
<tr>
<td height=2 bgcolor="0D0D0D">
<img src="wtv-home:/ROMCache/Spacer.gif" width=1 height=1>
</TABLE>
<table cellspacing=0 cellpadding=0>
<tr>
<td rowspan=2 abswidth=10>
<td absheight=10>
<tr>
<td abswidth=416 valign=top align=left>
Do you want to look for something else?<br>
<img src="/ROMCache/Spacer.gif" width=1 height=4>
<form action="News.aspx">
<input name="search" bgcolor=#202020 cursor=#cc9933 text="E7CE4A" font=proportional value="${request_headers.query.search}" SIZE=28 MAXLENGTH=100>
&nbsp;
<font color=E7CE4A><shadow>
<input type=submit borderimage="file://ROM/Borders/ButtonBorder2.bif" value="Look for" usestyle>
</shadow></font>
</form>
</table>
</BODY>
</HTML>`;
sendToClient(socket, headers, data);
}).catch((e) => {
// listGroups error
throwError(e);
});
}).catch((e) => {
// no connection
throwError(e);
});
}
if (!wtvnews.client) {
const errpage = wtvshared.doErrorPage();
headers = errpage[0];
data = errpage[1];
} else {
request_is_async = true;
if (request_headers.query.search) {
WebTVSearchGroups(request_headers.query.search)
} else if (request_headers.query.group) {
if (request_headers.query.article) {
WebTVShowMessage(request_headers.query.group, request_headers.query.article);
} else {
WebTVListGroup(request_headers.query.group);
}
} else {
// redirect to lobby if no understandable queries passed
headers = "300 OK\nLocation: wtv-news:/lobby";
sendToClient(socket, headers, null);
}
}

View File

@@ -0,0 +1,72 @@
const minisrv_service_file = true;
// max of 6, any more will be ignored
headers = `200 OK
Connection: Keep-Alive
Content-Type: text/html`
data = `<HTML>
<HEAD>
<DISPLAY fontsize=medium>
<TITLE>Featured discussion groups</TITLE>
</HEAD>
<body
bgcolor="191919" text="42BD52" link="189CD6"
vlink="189CD6"
hspace=0
vspace=0>
<table cellspacing=0 cellpadding=0>
<tr>
<td abswidth=10>
<td colspan=3>
<table cellspacing=0 cellpadding=0>
<tr>
<td valign=center absheight=80>
<font size="+2" color="E7CE4A"><blackface><shadow>
Featured discussions
</table>
<td abswidth=20>
<tr>
<td>
<td WIDTH=198 HEIGHT=200 VALIGN=top ALIGN=left>`;
const featuredGroups = minisrv_config.services[minisrv_config.services[service_name].usenet_service].featuredGroups;
const limit = 6;
while (featuredGroups.length > limit) featuredGroups.pop(); // remove anything passing our limit
function printGroup(group) {
return `<a href="News.aspx?group=${group.group}"><b>${group.name}</b></a><br>${group.description}<BR>`;
}
// evens
Object.keys(featuredGroups).forEach((k) => { if (k % 2 === 0) { data += printGroup(featuredGroups[k]); } });
if (featuredGroups.length > 1) data += `<td WIDTH=20><td WIDTH=198 HEIGHT=220 VALIGN=top ALIGN=left>`;
// odds
Object.keys(featuredGroups).forEach((k) => { if (k % 2 !== 0) data += printGroup(featuredGroups[k]); });
data += `
</table>
<hr>
<table cellspacing=0 cellpadding=0>
<tr>
<td rowspan=2 abswidth=10>
<td absheight=10>
<tr>
<td abswidth=416 valign=top align=left>
Type a discussion topic<br>
<img src="/ROMCache/Spacer.gif" width=1 height=4>
<form action="News.aspx" method="GET">
<input name="search" bgcolor=#202020 cursor=#cc9933 text="E7CE4A" font=proportional value="" SIZE=28 MAXLENGTH=100>
&nbsp;
<font color=E7CE4A><shadow>
<input type=submit borderimage="file://ROM/Borders/ButtonBorder2.bif" value="Look for" usestyle>
</shadow></font>
</form>
</table>
</BODY>
</HTML>`;

View File

@@ -53,6 +53,8 @@ data = `<HTML xmlns:msntv>
</p> </p>
<p style="display: inline; left: 76px; position: relative;">Example:</p> <p style="display: inline; left: 76px; position: relative;">Example:</p>
<p style="display: inline; left: 110px; position: relative;">ABCEAZ82KDKA</p> <p style="display: inline; left: 110px; position: relative;">ABCEAZ82KDKA</p>
<p>&nbsp;</p>
<p>One day, certain promotion codes will unlock special features or content on minisrv. But for now, this is just a placeholder page.</p>
</DIV> </DIV>
<div id="footer"> <div id="footer">
<msntv:CustomButton id="continue" label="Continue" href="javascript:SubmitForm()" /> <msntv:CustomButton id="continue" label="Continue" href="javascript:SubmitForm()" />

View File

@@ -86,7 +86,7 @@ data = `<HTML xmlns:msntv>
<tr style="margin: 0; padding: 0; top: 2px; position: relative;"> <tr style="margin: 0; padding: 0; top: 2px; position: relative;">
<td style="margin: 0; padding: 0; vertical-align: middle; top: 2px; position: relative;"><img src="msntv:/Shared/Images/BulletCustom.gif" height="14" width="7" alt="Bullet"></td> <td style="margin: 0; padding: 0; vertical-align: middle; top: 2px; position: relative;"><img src="msntv:/Shared/Images/BulletCustom.gif" height="14" width="7" alt="Bullet"></td>
<td style="margin: 0; padding: 0; width: 4px;"></td> <td style="margin: 0; padding: 0; width: 4px;"></td>
<td style="margin: 0; padding: 0; font:bold 18; line-height: 20px;"><a class="shrLnk2" href="https://headwaiter.trusted.msntv.msn.com/connection/boxcheck.html" style="display: inline-block; line-height: 20px;">I want to start over</a></td> <td style="margin: 0; padding: 0; font:bold 18; line-height: 20px;"><a class="shrLnk2" href="msntv:/Registration/pages/Welcome.html" style="display: inline-block; line-height: 20px;">I want to start over</a></td>
</tr> </tr>
</table> </table>
</div> </div>

View File

@@ -75,29 +75,42 @@ data = `<html xmlns:msntv>
</STYLE> </STYLE>
<script> <script>
var tvShell = new ActiveXObject("MSNTV.TVShell"); var TVShell = new ActiveXObject("MSNTV.TVShell");
tvShell.UserManager.SetCurrentUserIsAuthorized(false); TVShell.UserManager.SetCurrentUserIsAuthorized(false);
function AddUser() { function AddUser() {
var user = tvShell.UserManager.AddNew("${username}"); var user = TVShell.UserManager.AddNew("${username}");
if (user) { if (user) {
user.IsPersistent = true; user.IsPersistent = true;
user.setAttribute("GuestUser", false);
var dt = new Date();
TVShell.UserManager.LastLoginTime = dt.getTime() / 1000 + dt.getTimezoneOffset() * 60;
TVShell.UserManager.OfflineAppMaxAccessDays = 20;
TVShell.UserManager.OfflineAppMaxAccessTimes = 20;
TVShell.UserManager.CurrentUser = user;
user.LargeIcon = "msntv:/SignInPics/big/${picture}.png";
user.SmallIcon = "msntv:/SignInPics/small/${picture}.gif";
TVShell.UserManager.Save();
} else { } else {
user = tvShell.UserManager.Item("${username}"); user = TVShell.UserManager.Item("${username}");
if (user && !user.IsPersistent) { if (user && !user.IsPersistent) {
user.IsPersistent = true; user.IsPersistent = true;
} }
} }
if (user) {
user.LargeIcon = "msntv:/tvshell/images/${picture}.png";
user.SmallIcon = "msntv:/tvshell/images/${picture}.gif";
} }
function GobacktoSignon() {
entry = TVShell.ServiceList.Add('connection::login');
entry.URL = 'https://sg1.trusted.msntv.msn.com/connection/GatePage.aspx?phase=Bootstrap&purpose=Authorize';
entry.Description = '${minisrv_config.config.service_name}/sg1 [${minisrv_config.config.hide_minisrv_version ? "beta" : minisrv_version_string.replace("zefie's wtv minisrv ","")}]';
TVShell.ServiceList.Save();
TVShell.ConnectionManager.ServiceState = 'ReSignIn';
} }
AddUser(); AddUser();
tvShell.UserManager.Save();
</script> </script>
</head> </head>
@@ -116,7 +129,7 @@ data = `<html xmlns:msntv>
<table class="ApolloIcons" tabindex="-1"> <table class="ApolloIcons" tabindex="-1">
<tr height="70"> <tr height="70">
<td tabindex="-1"> <td tabindex="-1">
<span style='display:inline-block; width:142px; height:158px; behavior:url(#default#alphaImageLoader); src:url(msntv:/SignInPics/Big/${picture}.png);'></span> <span style='display:inline-block; width:142px; height:158px; behavior:url(#default#alphaImageLoader); src:url(msntv:/SignInPics/big/${picture}.png);'></span>
</td> </td>
</tr> </tr>
</table> </table>
@@ -139,7 +152,7 @@ data = `<html xmlns:msntv>
</p> </p>
<div id="footer"> <div id="footer">
<msntv:CustomButton id="continue" label="Continue" href="/Home/Home.aspx" /> <msntv:CustomButton id="continue" label="Continue" onclick="GobacktoSignon()" />
</div> </div>
</div> </div>
</body> </body>

View File

@@ -4,7 +4,7 @@ headers = `Status: 200 OK
Content-type: text/html`; Content-type: text/html`;
data = `<HTML xmlns:msntv> data = `<HTML xmlns:msntv>
<?import namespace="msntv" implementation="HTC/Shared/CustomButton.htc"> <?import namespace="msntv" implementation="https://sg1.trusted.msntv.msn.com/Include/HTC/Shared/CustomButton.htc">
<HEAD> <HEAD>
<title id="title">Learning to use the keyboard</title> <title id="title">Learning to use the keyboard</title>
<meta name="vs_targetSchema" content="http://schemas.microsoft.com/intellisense/ie5"> <meta name="vs_targetSchema" content="http://schemas.microsoft.com/intellisense/ie5">
@@ -32,26 +32,24 @@ data = `<HTML xmlns:msntv>
<BODY width="520" height="388"> <BODY width="520" height="388">
<DIV id="title">Logging into your account</DIV> <DIV id="title">Logging into your account</DIV>
<DIV id="main"> <DIV id="main">
<p>When using MSNTV2, you can access the same minisrv account as your WebTV.</p> <p>When using MSNTV2, you can use an existing minisrv account.</p>
<br> <br>
<p>Type your existing primary username in the box below:</p> <p>Type your existing primary username in the box below:</p>
<div class="input-container"> <div class="input-container">
<textarea name="username" id="username" rows="1" cols="15"></textarea> <textarea name="username" id="username" rows="1" cols="15"></textarea>
<p style="display: inline;">@${minisrv_config.config.service_name}</p> <p style="display: inline;">@${minisrv_config.config.domain_name}</p>
</div> </div>
<br> <br>
<div class="input-container"> <div class="input-container">
<textarea name="password" id="password" rows="1" cols="15"></textarea> <textarea name="password" id="password" rows="1" cols="15"></textarea>
</div> </div>
<br> <br>
If you don't have an existing minisrv account, select the <EM>Back</EM> Button. If you do not have a password set on your primary user account, you will need to set one up on your WebTV before you can connect it to this MSNTV2.
If you do not have a password set on your primary user account, you will need to set one up on your WebTV before you can log in here. After entering your username and password, select the <EM>Continue</EM> Button. After entering your username and password, select the <EM>Continue</EM> Button.
</DIV> </DIV>
<div id="footer"> <div id="footer">
<msntv:CustomButton id="continue" label="Continue" href="/Register/ConnectionType.aspx"/> <msntv:CustomButton id="continue2" label="Back" href="/Register/Establish-your-MSN-TV-Account.html"/>
</div> <msntv:CustomButton id="continue" label="Continue" disabled="true" href="/Register/Validate-account.aspx"/>
<div id="Back">
<msntv:CustomButton id="continue2" label="Back" href="/Register/ConnectionType.aspx"/>
</div> </div>
</BODY> </BODY>
</HTML>`; </HTML>`;

View File

@@ -1,6 +1,9 @@
const minisrv_service_file = true; const minisrv_service_file = true;
// TODO: if the user hits back, and removes the password from the user/pass page, any previously entered password
// will still be in the cookie and they will skip the password page.
// We should probably clear the password cookie when they hit back from the password page, but how?
// We can't assume password = '' means they hit back, because some pages will not send it.
let email = request_headers.query.email || ''; let email = request_headers.query.email || '';
if (Array.isArray(email)) email = email[0]; if (Array.isArray(email)) email = email[0];
if (!email && request_headers.cookie) { if (!email && request_headers.cookie) {
@@ -15,7 +18,7 @@ if (!password && request_headers.cookie) {
if (pm) password = decodeURIComponent(pm[1]); if (pm) password = decodeURIComponent(pm[1]);
} }
if (email && email.indexOf('@') < 0) email += "@"+minisrv_config.config.service_name; if (email && email.indexOf('@') < 0) email += "@"+minisrv_config.config.domain_name;
let userAvail = false; let userAvail = false;
if (email) { if (email) {
@@ -31,14 +34,16 @@ data = "";
} }
} }
if (!password) { if (!password && !headers) {
headers = `Status: 302 Found headers = `Status: 302 Found
Location: https://sg1.trusted.msntv.msn.com/Register/Password-Required.aspx`; Location: https://sg1.trusted.msntv.msn.com/Register/Password-Required.aspx`;
data = ""; data = "";
deleteCookie('register_password', { path: '/' });
} else {
setCookie('register_password', password, { path: '/' });
} }
if (userAvail && password) { if (userAvail && password) {
if (password) setCookie('register_password', password, { path: '/' });
headers = `Status: 200 OK headers = `Status: 200 OK
Content-type: text/html`; Content-type: text/html`;
@@ -75,7 +80,7 @@ data = `<HTML xmlns:msntv>
<tr style="margin: 0; padding: 0; top: 2px; position: relative;"> <tr style="margin: 0; padding: 0; top: 2px; position: relative;">
<td style="margin: 0; padding: 0; vertical-align: middle; top: 2px; position: relative;"><img src="msntv:/Shared/Images/BulletCustom.gif" height="14" width="7" alt="Bullet"></td> <td style="margin: 0; padding: 0; vertical-align: middle; top: 2px; position: relative;"><img src="msntv:/Shared/Images/BulletCustom.gif" height="14" width="7" alt="Bullet"></td>
<td style="margin: 0; padding: 0; width: 4px;"></td> <td style="margin: 0; padding: 0; width: 4px;"></td>
<td style="margin: 0; padding: 0; font-size: 24px; font:bold 18; line-height: 20px;"><a class="shrLnk2" href="/Register/Enter-Promotion-Code.aspx" style="display: inline-block; line-height: 20px;">Yes, I have a Promotion Code.</a></td> <td style="margin: 0; padding: 0; font-size: 24px; font:bold 18; line-height: 20px;"><a class="shrLnk2" href="/Register/Enter-Promotion-Code.aspx" style="display: inline-block; line-height: 20px;">Yes, I have a Promotion Code.</a> (TODO)</td>
</tr> </tr>
</table> </table>
</div> </div>

View File

@@ -51,7 +51,7 @@ data = `<HTML xmlns:msntv>
<p>Type your minisrv username:</p> <p>Type your minisrv username:</p>
<div class="input-container"> <div class="input-container">
<td><input type="text" id="email" class="inputText" name="email" maxlength="32" size="25"> </td> <td><input type="text" id="email" class="inputText" name="email" maxlength="32" size="25"> </td>
<p style="display: inline; bottom: 4px; position: relative;">@${minisrv_config.config.service_name}</p> <p style="display: inline; bottom: 4px; position: relative;">@${minisrv_config.config.domain_name}</p>
</div> </div>
<br> <br>
<p>Next, enter a password:</p> <p>Next, enter a password:</p>

View File

@@ -6,7 +6,7 @@ Content-type: text/html`;
data = `<HTML xmlns:msntv> data = `<HTML xmlns:msntv>
<?import namespace="msntv" implementation="/Include/HTC/Shared/CustomButton.htc"> <?import namespace="msntv" implementation="/Include/HTC/Shared/CustomButton.htc">
<HEAD> <HEAD>
<title id="title">Login to Passport</title> <title id="title">Username Not Available</title>
<meta name="vs_targetSchema" content="http://schemas.microsoft.com/intellisense/ie5"> <meta name="vs_targetSchema" content="http://schemas.microsoft.com/intellisense/ie5">
<link rel="stylesheet" type="text/css" href="msntv:/Registration/css/Registration.css"> <link rel="stylesheet" type="text/css" href="msntv:/Registration/css/Registration.css">
<STYLE> <STYLE>

View File

@@ -0,0 +1,719 @@
const minisrv_service_file = true;
// Get the phase parameter from the query string
let phase = request_headers.query.phase;
if (Array.isArray(phase)) phase = phase[0];
let BoxId = request_headers.query.BoxId;
if (Array.isArray(BoxId)) BoxId = BoxId[0];
let clientIp = socket.remoteAddress;
let banned = false;
let sessionId = null;
ServiceDomain = minisrv_config.config.domain_name;
// Use the shared MSNTV2 helper injected by WTV-MSNTV2 VM context.
if (BoxId) {
if (!BoxId || BoxId.length != 20 || !/^\d+$/.test(BoxId))
{
console.warn("Invalid BoxId format "+BoxId+" from "+clientIp);
banned = true;
} else {
sessionId = encodeSessionID(BoxId);
}
} else if (request_headers.cookie && request_headers.cookie.SessionID) {
BoxID = decodeSessionID(request_headers.cookie.SessionID);
sessionId = request_headers.cookie.SessionID;
} else {
console.warn("No BoxId provided by client "+clientIp);
banned = true;
}
if (!sessionId && !banned) {
banned = true;
}
if (!session_data && BoxId) {
console.log("Missing session_data for BoxId %s", BoxId);
}
let registered = false;
let username = '';
let Profile_Picture = '';
if (session_data) {
registered = session_data.isRegistered();
if (registered) {
username = session_data.getSessionData("subscriber_username") || '';
Profile_Picture = session_data.getSessionData('ProfilePicture') || '';
}
}
// Current UTC time
const now = new Date();
const timeData = {
hh: now.getUTCHours(),
mm: now.getUTCMinutes(),
ss: now.getUTCSeconds(),
mo: now.getUTCMonth() + 1,
dd: now.getUTCDate(),
yyyy: now.getUTCFullYear()
};
const timezoneMap = {
"UTC": {
standardName: "UTC",
standardOffset: 0,
daylightName: "UTC",
daylightOffset: 0
}
};
const {
standardName,
standardOffset,
daylightName,
daylightOffset
} = timezoneMap["UTC"];
// Set session cookie on the client
if (sessionId) {
setCookie('SessionID', sessionId, { path: '/' });
}
// Handle different phases
switch (phase) {
case "Bootstrap":
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>`;
break;
case "BoxCheck":
headers = `200 OK
Content-type: text/html`;
data = `<html>
<head>
<title id="title"></title>
</head>
<body>
<iframe id="checkmail" style="display:none"></iframe>
<script src="msntv:/Javascript/TVShell.js" language="javascript"></script>
<script src="msntv:/Javascript/ServiceList.js" language="javascript"></script>
<script src="msntv:/Javascript/GuestUser.js" language="javascript"></script>
<script language="javascript">
try {
var TVShell = new ActiveXObject("MSNTV.TVShell");
var Sink = new ActiveXObject("MSNTV.MultipleEventSink");
var email = TVShell.UserManager.EMail;
var wanProvider = TVShell.ConnectionManager.WANProvider;
var banned = ${banned};
var registered = ${registered};
var username = "${username}";
var picture = "${Profile_Picture}";
var ServiceDomain = "${ServiceDomain}";
var MSNTVToken = "";
var serviceArgs = new Array();
var ProductionArgs = new Array("msntv.msn.com", "MBI", 0, 0,
"mail.services.live.com", "MBI", 0, 0,
"livefilestore.com", "MBI", 0, 0,
"messenger.msn.com", "?id=507", 0, 0,
"spaces.live.com", "MBI", 0, 0
);
serviceArgs[0] = ProductionArgs;
if (!banned) {
// DEBUG ONLY! USE WITH CAUTION!
TVShell.AddSecretCode(10000); // Power-on for nightly update
TVShell.AddSecretCode(10001); // Power-on for nightly email check at anchor time
TVShell.AddSecretCode(10002); // Power-on for nightly email check at non-anchor time
TVShell.AddSecretCode(77437); // spooky dialing options
TVShell.AddSecretCode(93288); // Service Selection Page
TVShell.AddSecretCode(6145539); // crash the system
TVShell.AddSecretCode(3932397); // update loop test
}
function isIDCRLErrorCode( theCode )
{
// when high bit is set, it is an error
if ( theCode & 0x80000000 )
return true;
return false;
}
function getIDCRLCode( theCode )
{
return (theCode & 0xFF);
}
/*
function CheckForUser(usernameToCheck) {
var UserManager = TVShell.UserManager;
for (var i=0; i<UserManager.Count; i++) {
var user = UserManager.Item(i);
if (user == usernameToCheck) {
return true;
}
}
return false;
}*/
function DoLogin() {
var currentUser = TVShell.UserManager.CurrentUser;
if (currentUser == null) {
if (banned === true) {
var url = 'https://sg1.trusted.msntv.msn.com/connection/banned.html';
var myPanel = TVShell.PanelManager.Item('main');
if (myPanel) myPanel.GotoURL(url);
} else {
if (registered === true) {
var user = TVShell.UserManager.AddNew(username + '@' + ServiceDomain);
if (user) {
var useroptions = UserManager.Item(username + '@' + ServiceDomain);
useroptions.IsPersistent = true;
//user.setAttribute("GuestUser", false);
useroptions.LargeIcon = "msntv:/SignInPics/big/"+ picture + ".png";
useroptions.SmallIcon = "msntv:/SignInPics/small/"+ picture + ".gif";
TVShell.UserManager.Save();
}
}
var myPanel = TVShell.PanelManager.Item('main')
if (registered === true) {
entry = TVShell.ServiceList.Add('connection::login');
entry.URL = 'https://sg1.trusted.msntv.msn.com/connection/GatePage.aspx?phase=Bootstrap&purpose=Authorize';
entry.Description = '${minisrv_config.config.service_name}/sg1 [${minisrv_config.config.hide_minisrv_version ? "beta" : minisrv_version_string.replace("zefie's wtv minisrv ","")}]';
TVShell.ServiceList.Save();
var signon = TVShell.BuiltinServiceList.Item("SignOn");
var panel = TVShell.PanelManager.FocusedPanel;
var atLogin = false;
TVShell.ConnectionManager.ServiceState = 'ReSignIn';
if ( signon && panel && panel.Name == "main" )
{
if ( IsMainPanelOnPage( signon.URL ) ) atLogin = true;
}
if (!atLogin) {
myPanel.ClearTravelLog();
myPanel.NoBackToMe = true;
GotoSignOn();
}
} else {
if (myPanel) myPanel.GotoURL('https://sg1.trusted.msntv.msn.com/Register/Establish-your-MSN-TV-Account.html');
}
if (myPanel) {
myPanel.ClearTravelLog();
myPanel.NoBackToMe = true;
}
}
} else {
if (banned === true) {
var url = 'https://sg1.trusted.msntv.msn.com/connection/banned.html';
var myPanel = TVShell.PanelManager.Item('service');
if (myPanel) myPanel.GotoURL(url);
}
}
var hasIDCRL = false;
try {
hasIDCRL = (typeof TVShell.LoginManager.IDCRLInitialize === "unknown" ||
typeof TVShell.LoginManager.IDCRLInitialize === "function");
} catch (e) {
hasIDCRL = false;
}
if (currentUser != null) {
// Check if We can do IDCRL if not fall back to Legacy XMLlogin
try {
if (hasIDCRL) {
DoIDCRLLogin();
} else {
// Non IDCRL Auth Code (Pre 5.x)
Sink.AttachEvent(TVShell.LoginManager, 'OnLoginResult', OnLoginResult);
TVShell.LoginManager.PassportSiteIDs = '507';
TVShell.LoginManager.LoginURL = "https://login.live.com/ppsecure/clientpost.srf";
TVShell.LoginManager.LogoutURL = "https://login.live.com/ppsecure/logoutxml.srf";
TVShell.LoginManager.ResetPasswordURL = "https://login.live.com/ppsecure/MSRV_ResetPW_ClientPost.srf";
TVShell.LoginManager.ChangePasswordURL = "https://login.live.com/ppsecure/MSRV_ChangePW_ClientPost.srf";
TVShell.LoginManager.RequestProfileURL = "https://login.live.com/ppsecure/ClientProfileRequest.srf";
TVShell.LoginManager.UpdateProfileURL = "https://login.live.com/ClientEditProf.srf";
TVShell.LoginManager.Authenticate(email, "", "https://login.live.com/ppsecure/clientpost.srf");
}
} catch (e) {
TVShell.EventLog.Important("Login error: " + e.message);
}
}
}
function OnLoginResult(hr,t,p)
{
MSNTVToken = t;
GoToUserCheck();
}
function DoIDCRLLogin()
{
try {
TVShell.LoginManager.IDCRLInitialize(0);
Sink.AttachEvent(TVShell.LoginManager, "IDCRLOnAuthStateChanged",IDCRLOnAuthStateChanged);
TVShell.LoginManager.IDCRLLogonAndAuthToServices(serviceArgs[0]);
} catch (e) {
TVShell.EventLog.Important("IDCRL error: " + e.message);
}
}
function IDCRLOnAuthStateChanged(result, authState, requestStatus, user, serviceTarget, servicePolicy, token, webFlowUrl)
{
// Find the matching policy in ProductionArgs for this serviceTarget
var expectedPolicy = "";
for(var i = 0; i < ProductionArgs.length; i++) {
if(ProductionArgs[i] == serviceTarget && i+1 < ProductionArgs.length) {
expectedPolicy = ProductionArgs[i+1];
break;
}
}
// Now check with the correctly matched policy
if(TVShell.UserManager.CurrentUser.EMail != user ||
ProductionArgs[0] != serviceTarget ||
expectedPolicy != servicePolicy ||
(isIDCRLErrorCode(authState) || getIDCRLCode(authState) != 0x03) ||
(isIDCRLErrorCode(requestStatus) || getIDCRLCode(requestStatus) != 0x00)) {
return;
}
if (token != ""){
var tIndex = token.indexOf("t=");
var pIndex = token.indexOf("&p=");
// make sure there is only the "t" token exists, this is for RPS compact ticket
// it is possible that compact ticket contains empty p parameter.
MSNTVToken = token;
GoToUserCheck();
}
else
TVShell.EventLog.Important("No token");
}
function DoPoptimization() {
if (wanProvider === "MSNIANB") {
var connector = GetConnectorByName("LocalPOP");
if (connector == null) {
connector = TVShell.ConnectionManager.MSNIAManager.Connectors.Add("modem");
connector.AreaCode = "";
connector.Exchange = "";
connector.DialingFlags = 0x00001000;
connector.Name = "LocalPOP";
connector.LocationName = "LocalPOP";
TVShell.ConnectionManager.Save();
connector.Poptimize("0", connector.AreaCode, connector.Exchange);
}
if (connector.Phonebook == null || connector.Phonebook.length === 0) {
if (connector.AreaCode && connector.Exchange) {
connector.Poptimize(connector.AreaCode, connector.Exchange);
}
}
}
}
function GetConnectorByName(name) {
var connectors = TVShell.ConnectionManager.MSNIAManager.Connectors;
for (var i = 0; i < connectors.length; i++) {
if (connectors[i].Name === name) {
return connectors[i];
}
}
return null;
}
function GoToUserCheck() {
if (banned === true) {
var url = 'https://sg1.trusted.msntv.msn.com/connection/banned.html';
var myPanel = TVShell.PanelManager.Item('service');
if (myPanel) myPanel.GotoURL(url);
} else if (registered) {
var url = 'https://sg1.trusted.msntv.msn.com/connection/GatePage.aspx?phase=UserCheck&purpose=Authorize&t=' + MSNTVToken ;
var myPanel = TVShell.PanelManager.Item('service');
if (myPanel) myPanel.GotoURL(url);
}
}
function SetProgress(text, percent) {
if (progressPanel) {
progressPanel.Document.SetProgressText(text);
progressPanel.Document.SetProgressPercent(percent);
}
}
var progressPanel = TVShell.PanelManager.Item('progress');
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 {
DoPoptimization();
DoLogin();
try {
TVShell.DeviceControl.SetTimeZone(${standardOffset}, "${standardName}", 0, "");
} catch (e) {
TVShell.EventLog.Important("SetTimeZone error: " + e.message);
}
try {
TVShell.DeviceControl.SetClock(${timeData.hh}, ${timeData.mm}, ${timeData.ss}, ${timeData.mo}, ${timeData.dd}, ${timeData.yyyy});
} catch (e) {
TVShell.EventLog.Important("SetClock error: " + e.message);
}
}
} catch (e) {
TVShell.EventLog.Important("Error in boxcheck: " + e.message);
}
</script>
</body>
</html>`;
break;
case "UserCheck":
headers = `Content-type: text/html`;
// Check if the msntv.msn.com token is correct TODO
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");
var wanProvider = TVShell.ConnectionManager.WANProvider;
function SetServiceList() {
var entry;
// BuiltinServiceList - for main MSN TV services
TVShell.UserManager.CurrentUser.ServiceList.Clear(); //Always clear the list first to avoid dupes.
entry = TVShell.UserManager.CurrentUser.ServiceList.Add('help::help');
entry.URL = 'http://sg1.msntv.msn.com/health/Help.aspx';
entry.KeyCode = 0xAC; // VK_BROWSER_HOME
entry.Safe = true;
entry = TVShell.UserManager.CurrentUser.ServiceList.Add('home::home');
entry.URL = 'http://sg1.trusted.msntv.msn.com/Home/Home.aspx?WANProvider=' + wanProvider;
entry.KeyCode = 0xAC; // VK_BROWSER_HOME
entry.Safe = true;
entry = TVShell.UserManager.CurrentUser.ServiceList.Add('home::bgmusic');
entry.URL = '';
entry.Safe = true;
entry = TVShell.UserManager.CurrentUser.ServiceList.Add('home::backendproxy');
entry.URL = 'https://sg1.trusted.msntv.msn.com/BackendProxy';
entry.Safe = true;
entry = TVShell.UserManager.CurrentUser.ServiceList.Add('home::radioplus');
entry.URL = 'https://sg1.trusted.msntv.msn.com/Stations.xml';
entry.Safe = true;
entry = TVShell.ServiceList.Add('music::radiohome');
entry.URL = 'http://msntv.msn.com/pages/radio/home.aspx';
entry.Safe = true;
entry = TVShell.UserManager.CurrentUser.ServiceList.Add('Livefilestore::AuthServer');
entry.URL = 'livefilestore.com';
entry.Description = 'MBI'
entry.Safe = true;
entry = TVShell.UserManager.CurrentUser.ServiceList.Add('Skydrive::AuthServer');
entry.URL = 'favorites.live.com';
entry.Description = 'MBI'
entry = TVShell.UserManager.CurrentUser.ServiceList.Add('Skydrive::Browse');
// entry.URL = 'users.storage.live.com';
entry.URL = 'favorites.msn.com';
entry.Safe = true;
entry = TVShell.UserManager.CurrentUser.ServiceList.Add('Skydrive::AppId');
entry.Description = '1'
entry = TVShell.UserManager.CurrentUser.ServiceList.Add('Skydrive::ApiServer');
entry.URL = 'api.live.net';
entry.Safe = true;
entry = TVShell.UserManager.CurrentUser.ServiceList.Add('onlinestorage::root');
entry.URL = 'https://livefilestore/onlinestorage/';
entry.Safe = true;
entry = TVShell.UserManager.CurrentUser.ServiceList.Add('Favorites::RoamingServer');
entry.URL = 'https://livefilestore.com/onlinestorage/';
entry.Safe = true;
entry = TVShell.UserManager.CurrentUser.ServiceList.Add('Favorites::Migration');
entry.URL = 'https://livefilestore.com/onlinestorage/';
entry.Safe = true;
entry = TVShell.UserManager.CurrentUser.ServiceList.Add('Favorites::SyncServer');
entry.URL = 'https://livefilestore.com/onlinestorage/';
entry.Safe = true;
entry = TVShell.UserManager.CurrentUser.ServiceList.Add('onlinestorage::authServer');
entry.URL = 'http://77.68.90.130/';
entry.Description = 'MBI'
entry.Safe = true;
entry = TVShell.UserManager.CurrentUser.ServiceList.Add('mail::listmail');
entry.URL = 'http://mail-sgN.msntv.msn.com/apps/mail/listmail.aspx';
entry.KeyCode = 0xB4; // VK_LAUNCH_MAIL
entry.Safe = true;
entry = TVShell.UserManager.CurrentUser.ServiceList.Add('mail::writemail');
entry.URL = 'http://mail-sg1.trusted.msntv.msn.com/apps/mail/writemail.aspx';
entry.Safe = true;
entry = TVShell.UserManager.CurrentUser.ServiceList.Add('chat::home');
entry.URL = 'https://sg1.trusted.msntv.msn.com/Pages/Chat/Chat.aspx';
entry.Safe = true;
entry = TVShell.UserManager.CurrentUser.ServiceList.Add('chat::ServiceTarget');
entry.URL = 'chat.msn.com';
entry.Description = '?id=2260'
entry.Safe = true;
entry = TVShell.UserManager.CurrentUser.ServiceList.Add('messenger::root');
entry.URL = 'http://ms.msgrsvcs.ctsrv.gay:1863';
entry.Safe = true;
entry = TVShell.UserManager.CurrentUser.ServiceList.Add('messenger::passport');
entry.URL = 'https://login.live.com/messenger';
entry = TVShell.UserManager.CurrentUser.ServiceList.Add('messenger::ServiceTarget');
entry.URL = 'messenger.msn.com';
entry = TVShell.UserManager.CurrentUser.ServiceList.Add('search::search');
entry.URL = 'https://sg1.trusted.msntv.msn.com/Pages/Search/search.html';
entry.Safe = true;
entry = TVShell.UserManager.CurrentUser.ServiceList.Add('search::main');
entry.URL = 'https://sg1.msntv.msn.com/search/Search.aspx';
entry.Safe = true;
entry = TVShell.UserManager.CurrentUser.ServiceList.Add('discuss::home');
entry.URL = 'http://sg1.msntv.msn.com/apps/discuss/DiscussLobby.aspx';
entry.Safe = true;
entry = TVShell.UserManager.CurrentUser.ServiceList.Add('maps::main');
entry.URL = 'https://sg1.msntv.msn.com/apps/maps/GetMap.aspx';
entry.Safe = true;
entry = TVShell.UserManager.CurrentUser.ServiceList.Add('Settings::HomeNetwork');
entry.URL = 'msntv:/Settings/Network/HomeNetworking.html';
entry.Safe = true;
entry = TVShell.UserManager.CurrentUser.ServiceList.Add('settings::mainindex');
entry.URL = 'https://sg1.trusted.msntv.msn.com/apps/settings/MainIndex.aspx';
entry.Safe = true;
entry = TVShell.UserManager.CurrentUser.ServiceList.Add('UAM::UAMbase');
entry.URL = 'https://sg1.trusted.msntv.msn.com/apps/uam/pages/settings.aspx';
entry.Safe = true;
entry = TVShell.UserManager.CurrentUser.ServiceList.Add('Photo::Home');
entry.URL = 'msntv:/Photo/PhotoHome.html';
entry.Safe = true;
entry = TVShell.UserManager.CurrentUser.ServiceList.Add('Photos');
entry.URL = 'msntv:/Photo/PhotoHome.html';
entry.Safe = true;
// Add services to ServiceList
TVShell.ServiceList.Clear();
entry = TVShell.ServiceList.Add('home::cinemanow');
entry.URL = 'http://g.msn.com/5TVANDURIL/4000';
entry = TVShell.ServiceList.Add('msn::radioplus');
entry.URL = 'http://radio.msn.com/asx/generate';
entry = TVShell.ServiceList.Add('msn::musicnews');
entry.URL = 'http://www.msnbc.msn.com/id/3032433/';
entry = TVShell.ServiceList.Add('connection::popupcontrol');
entry.URL = 'https://sg1.trusted.msntv.msn.com/connection/PopupControlWhiteList.ashx';
entry = TVShell.ServiceList.Add('connection::reconnect');
entry.URL = 'https://sg1.trusted.msntv.msn.com/connection/GatePage.aspx?phase=Bootstrap&purpose=ReAuthorize';
entry = TVShell.ServiceList.Add('connection::nightly_login');
entry.URL = 'https://sg1.trusted.msntv.msn.com/connection/GatePage.aspx?phase=Bootstrap&purpose=Nightly';
entry = TVShell.ServiceList.Add('mail::check');
entry.URL = 'https://sg1.trusted.msntv.msn.com/apps/connection/CheckMail.aspx?phase=CheckMail&purpose=CheckMail';
entry.Safe = true;
if (wanProvider === "BYOA") {
entry = TVShell.ServiceList.Add('home::videoplus');
entry.URL = 'http://msntv.msn.com/pages/msnvideo/main.aspx';
entry.Safe = true;
}
if (wanProvider === "BYOA") {
entry = TVShell.ServiceList.Add('home::musicvideo');
entry.URL = 'http://msntv.msn.com/pages/msnvideo/main.aspx?p=music';
entry.Safe = true;
}
entry = TVShell.ServiceList.Add('connection::resetpassword');
entry.URL = 'https://sg1.trusted.msntv.msn.com/connection/GatePage.aspx?phase=Bootstrap&purpose=ResetPassword';
entry = TVShell.ServiceList.Add('connection::pagepatch');
entry.URL = 'https://sg1.trusted.msntv.msn.com/connection/PagePatch.ashx';
entry = TVShell.ServiceList.Add('connection::login');
entry.URL = 'https://sg1.trusted.msntv.msn.com/connection/GatePage.aspx?phase=Bootstrap&purpose=Authorize';
entry.Description = '${minisrv_config.config.service_name}/sg1 [${minisrv_config.config.hide_minisrv_version ? "beta" : minisrv_version_string.replace("zefie's wtv minisrv ","")}]';
entry = TVShell.ServiceList.Add('ctags::main');
entry.URL = 'http://c.msn.com/c.gif?di=1455&pi=68206&tp=http%3a%2f%2fmsntv.msn.com%2fclient%2f';
TVShell.ServiceList.Save();
}
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) {
TVShell.PanelManager.Item('main').GotoURL(TVShell.UserManager.CurrentUser.ServiceList.Item('home::home').URL);
TVShell.PanelManager.Item('main').ClearTravelLog();
TVShell.PanelManager.Item('main').NoBackToMe = true;
} else {
TVShell.ConnectionManager.ServiceState = 'ReSignIn';
}
}
if (!IsServicePanel()) {
DontContinue();
} else {
SetServiceList();
TVShell.UserManager.SetCurrentUserIsAuthorized(true);
TVShell.ConnectionManager.ServiceState = 'Authorized';
var dt = new Date();
TVShell.UserManager.LastLoginTime = dt.getTime() / 1000 + dt.getTimezoneOffset() * 60;
TVShell.UserManager.OfflineAppMaxAccessDays = 20;
TVShell.UserManager.OfflineAppMaxAccessTimes = 20;
TVShell.UserManager.Save();
TVShell.PanelManager.Item('main').GotoURL(TVShell.UserManager.CurrentUser.ServiceList.Item('home::home').URL);
TVShell.PanelManager.Item('main').ClearTravelLog();
TVShell.PanelManager.Item('main').NoBackToMe = true;
}
</script>
</body>
</html>`;
break;
default:
headers = `200 OK
Content-type: text/html`;
data = `<HTML>
<HEAD>
<title id="title"></title>
</HEAD>
</HTML>`;
break;
}

View File

@@ -197,10 +197,12 @@ if (minisrv_config.services["wtv-author"].max_pages) {
</table> </table>
<p>A maximum of <b>${minisrv_config.services["wtv-author"].max_pages}</b> pages can be created, regardless of publish status. <p>A maximum of <b>${minisrv_config.services["wtv-author"].max_pages}</b> pages can be created, regardless of publish status.
<br><br> <br><br>
Your published pages are available at<br> `
if (numofpages > 0) {
`Your published pages are available at<br>
<a href="http://${site}/${session_data.getSessionData("subscriber_username")}/">http://${site}/${session_data.getSessionData("subscriber_username")}/</a> <a href="http://${site}/${session_data.getSessionData("subscriber_username")}/">http://${site}/${session_data.getSessionData("subscriber_username")}/</a>
</table>` </table>`
}
} }
data += ` data += `
<SCRIPT language=JavaScript> <SCRIPT language=JavaScript>

View File

@@ -38,6 +38,7 @@ async function processLC2DownloadPage(flashrom_info, headers, numparts = null) {
if (!flashrom_info.part_count) flashrom_info.part_count = parseInt(flashrom_info.message.slice(flashrom_info.message.length - 4).replace(/\D/g, '')); if (!flashrom_info.part_count) flashrom_info.part_count = parseInt(flashrom_info.message.slice(flashrom_info.message.length - 4).replace(/\D/g, ''));
if (parseInt(flashrom_info.part_number) >= 0 && flashrom_info.rompath && flashrom_info.next_rompath) { if (parseInt(flashrom_info.part_number) >= 0 && flashrom_info.rompath && flashrom_info.next_rompath) {
if (!flashrom_info.message && flashrom_info.is_bootrom) { if (!flashrom_info.message && flashrom_info.is_bootrom) {
flashrom_info.part_count = 16;
flashrom_info.message = "BootRom Part " + (flashrom_info.part_number + 1) + " of " + flashrom_info.part_count; flashrom_info.message = "BootRom Part " + (flashrom_info.part_number + 1) + " of " + flashrom_info.part_count;
} }
@@ -73,6 +74,8 @@ async function processLC2DownloadPage(flashrom_info, headers, numparts = null) {
} }
session.lastDownloadTime = now; session.lastDownloadTime = now;
if (isNaN(downloadTime) || downloadTime < 1) downloadTime = 1;
headers = `200 OK headers = `200 OK
Content-type: text/html Content-type: text/html
@@ -127,7 +130,7 @@ Updating now
<font size=+1> <font size=+1>
Your ${session_data.getBoxName()} is being<br>updated automatically. Your ${session_data.getBoxName()} is being<br>updated automatically.
<p> <font size=+1> <p> <font size=+1>
This will take about ${downloadTime} minutes and<br>then you can use your ${session_data.getBoxName()} again. This will take about ${downloadTime} minute${downloadTime !== 1 ? "s" : ""} and<br>then you can use your ${session_data.getBoxName()} again.
`; `;
if (flashrom_info.is_bootrom && flashrom_info.part_number === (flashrom_info.part_count - 1)) { if (flashrom_info.is_bootrom && flashrom_info.part_number === (flashrom_info.part_count - 1)) {
data += `<p> data += `<p>

View File

@@ -37,7 +37,7 @@ if (minisrv_config.config.hide_incomplete_features) {
} }
/* We need to fix most webtv viewers for this, since they spoof a build that doesn't support messenger? /* We need to fix most webtv viewers for this, since they spoof a build that doesn't support messenger?
if (!session_data.hasCap("client-can-use-messenger")) { if (!session_data.capabilities.get("client-can-use-messenger")) {
removeSettingByUrl("wtv-setup:/messenger"); removeSettingByUrl("wtv-setup:/messenger");
} }
*/ */

View File

@@ -27,6 +27,7 @@ class WTVMSNTV2 {
this.tlsContext = this.loadTlsContext(); this.tlsContext = this.loadTlsContext();
this.forgeTlsCredentials = this.loadForgeTlsCredentials(); this.forgeTlsCredentials = this.loadForgeTlsCredentials();
this.server = net.createServer((socket) => this.handleConnection(socket)); this.server = net.createServer((socket) => this.handleConnection(socket));
this.tokens = {};
this.mimeTypes = { this.mimeTypes = {
html: 'text/html', html: 'text/html',
htm: 'text/html', htm: 'text/html',
@@ -129,7 +130,13 @@ class WTVMSNTV2 {
socket.rawDataListener = (chunk) => this.handleData(socket, chunk); socket.rawDataListener = (chunk) => this.handleData(socket, chunk);
socket.on('data', socket.rawDataListener); socket.on('data', socket.rawDataListener);
socket.on('error', (err) => { socket.on('error', (err) => {
if (this.service_config.debug) console.error('[WTV-MSNTV2] socket error:', err.message); if (this.service_config.debug) {
if (err.message == 'read ECONNRESET') {
console.warn('[WTV-MSNTV2] Client disconnected');
} else {
console.error('[WTV-MSNTV2] socket error:', err.message);
}
}
}); });
} }
@@ -1163,23 +1170,11 @@ class WTVMSNTV2 {
loadTlsContext() { loadTlsContext() {
try { try {
const certCandidates = [ const candidateCert = (this.service_config.ssl) ? this.wtvshared.parseConfigVars(this.service_config.ssl.cert) : null;
['msntv2/msn_domains.crt', 'msntv2/msn_domains.key'] const candidateKey = (this.service_config.ssl) ? this.wtvshared.parseConfigVars(this.service_config.ssl.key) : null;
]; if (!candidateCert || !candidateKey) return null;
let certFile = null; const certPem = fs.readFileSync(candidateCert);
let keyFile = null; const keyPem = fs.readFileSync(candidateKey);
for (const [certPath, keyPath] of certCandidates) {
const candidateCert = this.wtvshared.getServiceDep(certPath, true);
const candidateKey = this.wtvshared.getServiceDep(keyPath, true);
if (candidateCert && candidateKey) {
certFile = candidateCert;
keyFile = candidateKey;
break;
}
}
if (!certFile || !keyFile) return null;
const certPem = fs.readFileSync(certFile);
const keyPem = fs.readFileSync(keyFile);
return tls.createSecureContext({ return tls.createSecureContext({
cert: certPem, cert: certPem,
key: keyPem, key: keyPem,
@@ -1195,23 +1190,12 @@ class WTVMSNTV2 {
loadForgeTlsCredentials() { loadForgeTlsCredentials() {
try { try {
const certCandidates = [ const candidateCert = (this.service_config.ssl) ? this.wtvshared.parseConfigVars(this.service_config.ssl.cert) : null;
['msntv2/msn_domains.crt', 'msntv2/msn_domains.key'] const candidateKey = (this.service_config.ssl) ? this.wtvshared.parseConfigVars(this.service_config.ssl.key) : null;
];
let certFile = null; if (!candidateCert || !candidateKey) return null;
let keyFile = null; const certPem = fs.readFileSync(candidateCert, 'utf8');
for (const [certPath, keyPath] of certCandidates) { const keyPem = fs.readFileSync(candidateKey, 'utf8');
const candidateCert = this.wtvshared.getServiceDep(certPath, true);
const candidateKey = this.wtvshared.getServiceDep(keyPath, true);
if (candidateCert && candidateKey) {
certFile = candidateCert;
keyFile = candidateKey;
break;
}
}
if (!certFile || !keyFile) return null;
const certPem = fs.readFileSync(certFile, 'utf8');
const keyPem = fs.readFileSync(keyFile, 'utf8');
return { return {
certPem, certPem,
keyPem, keyPem,
@@ -1545,6 +1529,16 @@ class WTVMSNTV2 {
const self = this; const self = this;
const responseCookies = []; const responseCookies = [];
// try to make the debug name
let debug_name = (filepath) ? filepath.split(path.sep) : null;
if (debug_name) {
if (this.wtvshared.isConfiguredService(debug_name[debug_name.length - 2]))
// service:/filename
debug_name = debug_name[debug_name.length - 2] + ":/" + debug_name[debug_name.length - 1];
else
// filename
debug_name = debug_name[debug_name.length - 1];
}
const contextObj = { const contextObj = {
socket, socket,
request_headers, request_headers,
@@ -1568,6 +1562,7 @@ class WTVMSNTV2 {
cwd: path.dirname(filepath), cwd: path.dirname(filepath),
// Cookie helpers available to scripts // Cookie helpers available to scripts
response_cookies: responseCookies, response_cookies: responseCookies,
debug: require('debug')((debug_name) ? debug_name : 'service_script'),
setCookie(name, value, opts) { setCookie(name, value, opts) {
opts = opts || {}; opts = opts || {};
let s = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`; let s = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
@@ -1616,6 +1611,9 @@ class WTVMSNTV2 {
if (socket.ssid && this.ssid_sessions && vmResult.session_data !== undefined) { if (socket.ssid && this.ssid_sessions && vmResult.session_data !== undefined) {
this.ssid_sessions[socket.ssid] = vmResult.session_data; this.ssid_sessions[socket.ssid] = vmResult.session_data;
} }
if (vmResult.socket !== socket) {
socket = vmResult.socket;
}
if (!vmResult.request_is_async) { if (!vmResult.request_is_async) {
this._sendScriptResult(socket, request_headers, vmResult.headers, vmResult.data, responseCookies); this._sendScriptResult(socket, request_headers, vmResult.headers, vmResult.data, responseCookies);
} }

View File

@@ -56,18 +56,32 @@ class WTVClientSessionData {
this.loginWhitelist.push("wtv-head-waiter:/confirm-transfer"); this.loginWhitelist.push("wtv-head-waiter:/confirm-transfer");
} }
/**
* Assigns a new WTVMail instance to the session's mailstore property, using the current minisrv_config and session data.
*/
assignMailStore() { assignMailStore() {
this.mailstore = new WTVMail(this.minisrv_config, this) this.mailstore = new WTVMail(this.minisrv_config, this)
} }
/**
* Assigns a new WTVFavorites instance to the session's favstore property, using the current minisrv_config and session data.
*/
assignFavoriteStore() { assignFavoriteStore() {
this.mailstore = this.favstore = new WTVFavorites(this.minisrv_config, this) this.favstore = new WTVFavorites(this.minisrv_config, this)
} }
/**
* Creates a new WTVSec session. Used for RC4 SECURE ON requests.
* @returns {WTVSec} A new WTVSec session instance
*/
createWTVSecSession() { createWTVSecSession() {
return new WTVSec(this.minisrv_config) return new WTVSec(this.minisrv_config)
} }
/**
* Retrieves the total number of unread messages for the primary account.
* @returns {number} Number of unread messages
*/
getAccountTotalUnreadMessages() { getAccountTotalUnreadMessages() {
if (!this.isRegistered()) return false; // unregistered if (!this.isRegistered()) return false; // unregistered
if (this.user_id > 0) return false; // not primary user or pre-login if (this.user_id > 0) return false; // not primary user or pre-login
@@ -88,6 +102,9 @@ class WTVClientSessionData {
return total_unread_messages; return total_unread_messages;
} }
/**
* Clears all user session data from memory, including session store and data store, and resets mail and favorite stores.
*/
clearUserSessionMemory() { clearUserSessionMemory() {
this.setUserLoggedIn(false); this.setUserLoggedIn(false);
this.data_store = []; this.data_store = [];
@@ -96,6 +113,13 @@ class WTVClientSessionData {
this.assignMailStore() this.assignMailStore()
} }
/**
* Switches the current user ID and optionally updates related data stores.
* @param {number} user_id The user ID to switch to
* @param {boolean} update_mail Whether to update the mail store
* @param {boolean} update_ticket Whether to update the ticket data
* @param {boolean} update_favorite Whether to update the favorite store
*/
switchUserID(user_id, update_mail = true, update_ticket = true, update_favorite = true) { switchUserID(user_id, update_mail = true, update_ticket = true, update_favorite = true) {
this.user_id = parseInt(user_id); this.user_id = parseInt(user_id);
if (user_id !== null) { if (user_id !== null) {
@@ -143,6 +167,11 @@ class WTVClientSessionData {
return true; return true;
} }
/**
* Checks if a given mail address is in the address book.
* @param {string} addr The mail address to check against the address book
* @returns {boolean} True if the address is in the address book, false otherwise
*/
isAddressInAddressBook(addr) { isAddressInAddressBook(addr) {
const addresses = this.getSessionData("address_book"); const addresses = this.getSessionData("address_book");
if (addresses) { if (addresses) {
@@ -156,6 +185,11 @@ class WTVClientSessionData {
return false; return false;
} }
/**
* Finds the first available user slot for a new user.
* Can only be used by the primary account (user_id 0).
* @returns {number|boolean} The first available user slot index, or false if no slots are available
*/
findFreeUserSlot() { findFreeUserSlot() {
if (this.user_id !== 0) return false; // subscriber only command if (this.user_id !== 0) return false; // subscriber only command
const master_directory = this.getUserStoreDirectory(true); const master_directory = this.getUserStoreDirectory(true);
@@ -170,16 +204,30 @@ class WTVClientSessionData {
return false; return false;
} }
/**
* Returns the display name of the current user.
* @returns {string} The subscriber's display name if user_id is 0, otherwise the current user's display name.
*/
getDisplayName() { getDisplayName() {
return (this.user_id === 0) ? this.getSessionData("subscriber_name") : this.getSessionData("display_name"); return (this.user_id === 0) ? this.getSessionData("subscriber_name") : this.getSessionData("display_name");
} }
/**
* Gets the number of users for this SSID.
* Can only be used by the primary account (user_id 0).
* @returns {number} The number of users this SSID has
*/
getNumberOfUserAccounts() { getNumberOfUserAccounts() {
if (!this.isRegistered()) return false; if (!this.isRegistered()) return false;
if (this.user_id !== 0) return false; // subscriber only command if (this.user_id !== 0) return false; // subscriber only command
return Object.keys(this.listPrimaryAccountUsers()).length; return Object.keys(this.listPrimaryAccountUsers()).length;
} }
/**
* Lists all primary account users for this SSID.
*
* @returns {Array} An array containing the account data of all users for this SSID
*/
listPrimaryAccountUsers() { listPrimaryAccountUsers() {
if (this.user_id !== 0) return false; // subscriber only command if (this.user_id !== 0) return false; // subscriber only command
@@ -206,6 +254,10 @@ class WTVClientSessionData {
return account_data; return account_data;
} }
/**
* Recursively creates directories for the given path.
* @param {string} thedir The directory path to create
*/
mkdirRecursive(thedir) { mkdirRecursive(thedir) {
thedir.split(this.path.sep).reduce( thedir.split(this.path.sep).reduce(
(directories, directory) => { (directories, directory) => {
@@ -230,7 +282,7 @@ class WTVClientSessionData {
/** /**
* Finds an account's SSID and User ID from just the username * Finds an account's SSID and User ID from just the username
* @param {string} username The username to search for * @param {string} username The username to search for
* @returns {Array} [found {boolean}, account_dir {string|null}, user_id {number|null}] * @returns {Array} [found {boolean}, ssid {string|null}, user_id {number|null}]
*/ */
findAccountByUsername(username) { findAccountByUsername(username) {
const accounts_dir = this.getAccountStoreDirectory(); const accounts_dir = this.getAccountStoreDirectory();
@@ -307,6 +359,13 @@ class WTVClientSessionData {
return false; return false;
} }
/**
* A part of the account transfer process, creates a pending transfer file in both the source and target account
* store directories with the relevant SSID and transfer type (source or target) for each account.
* This allows the transfer process to be completed or cancelled later, and ensures that only accounts
* with a pending transfer can complete the transfer process.
* @param {string} ssid
*/
setPendingTransfer(ssid) { setPendingTransfer(ssid) {
const pending_file = this.getUserStoreDirectory(true) + this.path.sep + "pending_transfer.json"; const pending_file = this.getUserStoreDirectory(true) + this.path.sep + "pending_transfer.json";
let ssidobj = { "ssid": ssid, "type": "source" }; let ssidobj = { "ssid": ssid, "type": "source" };
@@ -319,6 +378,10 @@ class WTVClientSessionData {
this.fs.writeFileSync(dest_pending_file, JSON.stringify(ssidobj)); this.fs.writeFileSync(dest_pending_file, JSON.stringify(ssidobj));
} }
/**
* Cancels a pending account transfer, if it exists.
* @returns {string|null} The SSID of the cancelled transfer if a pending transfer was found and cancelled, or null if no pending transfer was found
*/
cancelPendingTransfer() { cancelPendingTransfer() {
const pending_file = this.getUserStoreDirectory(true) + this.path.sep + "pending_transfer.json"; const pending_file = this.getUserStoreDirectory(true) + this.path.sep + "pending_transfer.json";
if (this.fs.existsSync(pending_file)) { if (this.fs.existsSync(pending_file)) {
@@ -334,6 +397,10 @@ class WTVClientSessionData {
return null; return null;
} }
/**
* Finalize the transfer, completely moving all user data from the source account to the target account, and removing the pending transfer files.
* @returns {boolean} Success of the transfer
*/
finalizePendingTransfer() { finalizePendingTransfer() {
const pending_file = this.getUserStoreDirectory(true) + this.path.sep + "pending_transfer.json"; const pending_file = this.getUserStoreDirectory(true) + this.path.sep + "pending_transfer.json";
const file = this.fs.readFileSync(pending_file) const file = this.fs.readFileSync(pending_file)
@@ -353,13 +420,18 @@ class WTVClientSessionData {
return true; return true;
} }
/**
* Check if there is a pending transfer for this account, and optionally check if the pending transfer type matches the specified dtype.
* @param {string} dtype {source|target} If specified, only returns the SSID if the pending transfer type matches the specified dtype. If null, returns the pending transfer object with ssid and type.
* @returns {string|object|boolean} The SSID of the pending transfer if dtype matches, the pending transfer object if dtype is null, or false if no pending transfer is found
*/
hasPendingTransfer(dtype = null) { hasPendingTransfer(dtype = null) {
const pending_file = this.getUserStoreDirectory(true) + this.path.sep + "pending_transfer.json"; const pending_file = this.getUserStoreDirectory(true) + this.path.sep + "pending_transfer.json";
if (this.fs.existsSync(pending_file)) { if (this.fs.existsSync(pending_file)) {
const ssidobj = JSON.parse(this.fs.readFileSync(pending_file)); const ssidobj = JSON.parse(this.fs.readFileSync(pending_file));
console.log(ssidobj) console.log(ssidobj)
if (dtype) { if (dtype) {
(ssidobj.type === dtype) ? ssidobj.ssid : false; return (ssidobj.type === dtype) ? ssidobj.ssid : false;
} }
else { else {
return ssidobj; return ssidobj;
@@ -402,6 +474,10 @@ class WTVClientSessionData {
return result !== false; return result !== false;
} }
/**
* Checks if the user has a scrapbook directory.
* @returns {boolean} True if the scrapbook directory exists, false otherwise.
*/
scrapbookExists() { scrapbookExists() {
if (this.scrapbook_dir === null) { if (this.scrapbook_dir === null) {
const userstore_dir = this.getUserStoreDirectory(); const userstore_dir = this.getUserStoreDirectory();
@@ -411,6 +487,10 @@ class WTVClientSessionData {
return this.fs.existsSync(this.scrapbook_dir); return this.fs.existsSync(this.scrapbook_dir);
} }
/**
* Creates a scrapbook directory for the user if it does not already exist.
* @returns {boolean} Success of the creation
*/
createScrapbook() { createScrapbook() {
if (!this.scrapbookExists()) { if (!this.scrapbookExists()) {
try { try {
@@ -421,6 +501,10 @@ class WTVClientSessionData {
return false return false
} }
/**
* A wrapper that returns the scrapbook directory, and creates it if it doesn't exist.
* @returns {string} The path to the scrapbook directory
*/
scrapbookDir() { scrapbookDir() {
if (!this.scrapbookExists()) { if (!this.scrapbookExists()) {
this.createScrapbook(); this.createScrapbook();
@@ -428,6 +512,10 @@ class WTVClientSessionData {
return this.scrapbook_dir; return this.scrapbook_dir;
} }
/**
* List the files in the user's scrapbook directory, sorted in ascending order, and excluding any .meta files.
* @returns {Array} A filelist of the user's scrapbook files
*/
listScrapbook() { listScrapbook() {
if (!this.scrapbookExists()) { if (!this.scrapbookExists()) {
this.createScrapbook(); this.createScrapbook();
@@ -442,6 +530,10 @@ class WTVClientSessionData {
return filteredFiles; return filteredFiles;
} }
/**
* Finds the next available ID slot for a new scrapbook entry.
* @returns {number} An available ID slot
*/
getFreeScrapbookID() { getFreeScrapbookID() {
if (!this.scrapbookExists()) { if (!this.scrapbookExists()) {
this.createScrapbook(); this.createScrapbook();
@@ -458,6 +550,10 @@ class WTVClientSessionData {
return id; return id;
} }
/**
* Calculates the total size of the user's scrapbook directory.
* @returns {number} The total size in bytes
*/
getScrapbookUsage() { getScrapbookUsage() {
if (!this.scrapbookExists()) { if (!this.scrapbookExists()) {
this.createScrapbook(); this.createScrapbook();
@@ -475,6 +571,10 @@ class WTVClientSessionData {
return total_size; return total_size;
} }
/**
* Calculates the percentage of the scrapbook storage space that is in use.
* @returns {number} Percentage of the scrapbook storage space that is in use, out of the total allotted.
*/
getScrapbookUsagePercent() { getScrapbookUsagePercent() {
if (!this.scrapbookExists()) { if (!this.scrapbookExists()) {
this.createScrapbook(); this.createScrapbook();
@@ -486,6 +586,11 @@ class WTVClientSessionData {
return Math.round(usage_percent, 2); return Math.round(usage_percent, 2);
} }
/**
* Get a scrapbook image by its ID.
* @param {number} id
* @returns {Buffer|null} The image data as a Buffer, or null if the image does not exist
*/
getScrapbookImage(id) { getScrapbookImage(id) {
if (!this.scrapbookExists()) { if (!this.scrapbookExists()) {
this.createScrapbook(); this.createScrapbook();

View File

@@ -70,7 +70,7 @@ class WTVFTP {
stream.on('data', (chunk) => { stream.on('data', (chunk) => {
chunks.push(chunk); chunks.push(chunk);
totalsize += chunk.length; totalsize += chunk.length;
if (totalsize > 1024 * 1024 * 4) { if (totalsize > 1024 * 1024 * this.minisrv_config.services[this.service_name].max_response_size) {
this.sendToClient(socket, { 'Status': '413 The item chosen contains too much information to be used.', 'Content-Type': 'text/plain' }, 'Item too large'); this.sendToClient(socket, { 'Status': '413 The item chosen contains too much information to be used.', 'Content-Type': 'text/plain' }, 'Item too large');
ftpClient.end(); ftpClient.end();
return; return;

View File

@@ -21,6 +21,7 @@ class WTVShared {
process = require('process'); process = require('process');
shenanigans = null; shenanigans = null;
appdir = this.path.resolve(__dirname + this.path.sep + ".." + this.path.sep + ".."); appdir = this.path.resolve(__dirname + this.path.sep + ".." + this.path.sep + "..");
tokens = {};
minisrv_config = []; minisrv_config = [];
@@ -629,6 +630,97 @@ class WTVShared {
return this.fixPathSlashes(check_path); return this.fixPathSlashes(check_path);
} }
/**
* Get the session data (BoxID, UserID, creation time, expiry) associated with a given token, also deletes the token if expired.
* @param {string} token
* @return {object|null} { boxID, userId, created, expires } for the token, or null if token is invalid/expired
*/
getTokenData(token) {
const session = this.tokens[token];
if (session && session.expires > Date.now()) {
return { boxID: session.boxID, userId: session.userId, created: session.timestamp, expires: session.expires };
}
this.deleteToken(token);
return null;
}
/**
* Checks if a token is valid (exists and not expired)
* @param {string} token The token to check
* @returns {boolean} true if valid, false if not
*/
isTokenValid(token) {
const session = this.tokens[token];
if (session && session.expires > Date.now()) {
return true;
}
this.deleteToken(token);
return false;
}
/**
* Deletes a token from the token store.
* @param {string} token The token to delete
*/
deleteToken(token) {
delete this.tokens[token];
this.saveTokens();
}
mkdirRecursive(dirPath) {
if (!this.path.isAbsolute(dirPath)) {
dirPath = this.getAbsolutePath(this.parentDirectory + this.path.sep + dirPath);
}
const parts = dirPath.split(this.path.sep);
let currentPath = '';
for (const part of parts) {
if (part) {
if (currentPath === '') {
currentPath = part;
} else {
currentPath += this.path.sep + part;
}
if (!this.fs.existsSync(currentPath)) {
try {
this.fs.mkdirSync(currentPath);
} catch (e) {
if (e.code !== 'EEXIST') {
throw e;
}
}
}
}
}
}
saveTokens() {
const session_store = this.minisrv_config.config.SessionStore + this.path.sep + "msntv2";
if (!this.fs.existsSync(session_store)) {
this.mkdirRecursive(session_store);
}
const tokenFile = this.getAbsolutePath(this.path.join(session_store, `tokens.json`));
this.fs.writeFile(tokenFile, JSON.stringify(this.tokens), (err) => {
if (err) {
console.error('[WTV-MSNTV2] Error writing token file:', err);
}
});
}
/**
* Store a token with its associated BoxID and UserID.
* @param {string} token
* @param {string} boxID
* @param {number} userId
* @param {string|null} expiresTime - Optional expiration time for the token, otherwise uses server config defaults
*/
storeToken(token, boxID, userId, expiresTime = null) {
delete this.tokens[token]; // ensure any existing token with the same value is removed before storing new data
this.tokens[token] = { boxID, userId, timestamp: Date.now(), expires: expiresTime ? new Date(expiresTime).getTime() : Date.now() + (this.minisrv_config.services[this.service_name]?.token_expiry || 3600) * 1000 }; // 1 hour expiry
this.saveTokens();
this.debug(" * MSNTV2 stored token for BoxID %s (UserID: %s), token expires in %d seconds", boxID, userId, (this.tokens[token].expires - Date.now()) / 1000);
}
/** /**
* Detects if the client is in MiniBrowser mode * Detects if the client is in MiniBrowser mode
* @param {object} ssid_session * @param {object} ssid_session
@@ -1130,6 +1222,7 @@ class WTVShared {
// DON'T USE THIS // DON'T USE THIS
// Saved for reference until I come up with a better way // Saved for reference until I come up with a better way
// If used, this will exceed the stack limit over time // If used, this will exceed the stack limit over time
/*
unloadModule(moduleName) { unloadModule(moduleName) {
// Prevent usage // Prevent usage
return; return;
@@ -1141,6 +1234,7 @@ class WTVShared {
delete require.cache[resolvedPath]; delete require.cache[resolvedPath];
} }
} }
*/
/** /**
* Returns an absolute path without an trailing path seperator * Returns an absolute path without an trailing path seperator

View File

@@ -351,6 +351,7 @@
"ftp": { "ftp": {
"port": 1650, "port": 1650,
"connections": 3, "connections": 3,
"max_response_size": 8, // Megabytes
"handler_module": "WTVFTP", "handler_module": "WTVFTP",
"handler_extra_vars": ["wtvmime"] "handler_extra_vars": ["wtvmime"]
}, },
@@ -521,9 +522,15 @@
], ],
"handler_extra_vars": ["runScriptInVM", "handlePHP", "handleCGI", "ssid_sessions", "WTVClientSessionData", "socket_sessions"], "handler_extra_vars": ["runScriptInVM", "handlePHP", "handleCGI", "ssid_sessions", "WTVClientSessionData", "socket_sessions"],
"show_verbose_errors": false, "show_verbose_errors": false,
"ssl": {
"cert": "%ServiceDeps%/msntv2/minisrv.crt",
"key": "%ServiceDeps%/msntv2/minisrv.key"
},
"modules": [ "modules": [
"WTVRegister" "WTVRegister",
] "WTVNews"
],
"usenet_service": "wtv-news"
} }
}, },
"favorites": { "favorites": {

View File

@@ -0,0 +1,26 @@
[
{
"id": "session_data.hasCap",
"pattern": "session\\_data\\.hasCap\\s*\\(",
"flags": "g",
"message": "session_data.hasCap() is deprecated and will be removed",
"removeVersion": "0.9.80",
"replacement": "Use session_data.capabilities.get() instead"
},
{
"id": "getServiceString",
"pattern": "(?<!wtvshared\\.)getServiceString\\s*\\(",
"flags": "g",
"message": "getServiceString() is deprecated and will be removed",
"removeVersion": "0.9.80",
"replacement": "Use wtvshared.getServiceString() instead"
},
{
"id": "moveArrayKey",
"pattern": "(?<!wtvshared\\.)moveArrayKey\\s*\\(",
"flags": "g",
"message": "moveArrayKey() is deprecated and will be removed",
"removeVersion": "0.9.80",
"replacement": "Use wtvshared.moveArrayKey() instead"
}
]

View File

@@ -9,7 +9,8 @@
"start": "node app.js", "start": "node app.js",
"test": "node test.js", "test": "node test.js",
"debug": "cross-env DEBUG=* node app.js", "debug": "cross-env DEBUG=* node app.js",
"modem-proxy": "node modem_proxy.js" "modem-proxy": "node modem_proxy.js",
"scan-service-deprecations": "node tools/scan_service_vault_deprecations.js"
}, },
"author": { "author": {
"name": "zefie", "name": "zefie",

View File

@@ -225,45 +225,33 @@ function buildWebTVPS(videoES, audioES, outputPath, audioIntervalOverride, baHea
return false; return false;
} }
// Match known-working WebTV cadence (attract.mpg is ~1 audio per 7 video packs) // Use natural A/V ratio — matches actual bitrate split in the encoded file.
const inferredInterval = Math.max(1, Math.round(vChunks.length / aChunks.length)); // attract.mpg uses 7 because its video bitrate is ~7x its audio bitrate.
// Our encoded video is lower bitrate so the natural ratio is ~3.
const naturalInterval = Math.max(1, Math.round(vChunks.length / aChunks.length));
const audioInterval = Number.isFinite(audioIntervalOverride) && audioIntervalOverride > 0 const audioInterval = Number.isFinite(audioIntervalOverride) && audioIntervalOverride > 0
? Math.floor(audioIntervalOverride) ? Math.floor(audioIntervalOverride)
: Math.max(1, Math.round((inferredInterval + 7) / 2)); : naturalInterval;
console.log(`[*] ${vChunks.length} video chunks, ${aChunks.length} audio chunks, ` + console.log(`[*] ${vChunks.length} video chunks, ${aChunks.length} audio chunks, ` +
`1 audio per ~${audioInterval} video`); `1 audio per ~${audioInterval} video`);
const packs = []; const packs = [];
let aIdx = 0; let aIdx = 0;
// Pre-fill: 3 audio packs to prime the WebTV audio buffer // Pre-fill: 3 audio packs to prime the WebTV audio buffer (matches attract.mpg)
const preFill = Math.min(3, aChunks.length); const preFill = Math.min(3, aChunks.length);
for (let k = 0; k < preFill; k++, aIdx++) { for (let k = 0; k < preFill; k++, aIdx++) {
packs.push(makePack(0xC0, aChunks[aIdx])); packs.push(makePack(0xC0, aChunks[aIdx]));
} }
// Simple fixed-interval interleave: emit audioInterval video packs, then 1 audio pack
let vIdx = 0; let vIdx = 0;
// Spread audio over the full video timeline to avoid starving early playback
// and dumping remaining audio at EOF.
while (vIdx < vChunks.length || aIdx < aChunks.length) { while (vIdx < vChunks.length || aIdx < aChunks.length) {
if (vIdx >= vChunks.length) { for (let k = 0; k < audioInterval && vIdx < vChunks.length; k++) {
packs.push(makePack(0xC0, aChunks[aIdx++]));
continue;
}
if (aIdx >= aChunks.length) {
packs.push(makePack(0xE0, vChunks[vIdx++])); packs.push(makePack(0xE0, vChunks[vIdx++]));
continue;
} }
if (aIdx < aChunks.length) {
const videoProgress = vIdx / vChunks.length;
const audioProgress = aIdx / aChunks.length;
// Prefer video until audio falls behind target cadence.
if (audioProgress + (1 / Math.max(1, audioInterval * aChunks.length)) < videoProgress) {
packs.push(makePack(0xC0, aChunks[aIdx++])); packs.push(makePack(0xC0, aChunks[aIdx++]));
} else {
packs.push(makePack(0xE0, vChunks[vIdx++]));
} }
} }

View File

@@ -7,12 +7,30 @@ const forge = require('node-forge');
const workspaceRoot = __dirname; const workspaceRoot = __dirname;
const httpsDir = path.join(workspaceRoot, '..', 'includes', 'ServiceDeps', 'https'); const httpsDir = path.join(workspaceRoot, '..', 'includes', 'ServiceDeps', 'https');
const msnDir = path.join(workspaceRoot, '..', 'includes', 'ServiceDeps', 'msntv2'); const msnDir = path.join(workspaceRoot, '..', 'includes', 'ServiceDeps', 'msntv2');
const domainsFile = path.join(msnDir, 'msn_domains.txt');
const defaultCaCertPath = path.join(msnDir, 'msntv2.crt'); const domains = [
const defaultCaKeyPath = path.join(msnDir, 'msntv2.key'); "headwaiter.trusted.msntv.msn.com",
const defaultOutCertPath = path.join(msnDir, 'msn_domains.crt'); "sg1.trusted.msntv.msn.com",
const defaultOutKeyPath = path.join(msnDir, 'msn_domains.key'); "sg2.trusted.msntv.msn.com",
"sg3.trusted.msntv.msn.com",
"sg4.trusted.msntv.msn.com",
"msntv.msn.com",
"mail.services.live.com",
"login.live.com",
"poptimize.msn.com",
"favorites.msn.com",
"messenger.msn.com",
"livefilestore.com",
"users.storage.live.com",
"g.msn.com",
"msnialogin.passport.com",
"minisrv.local"
]
const defaultCaCertPath = path.join(msnDir, 'emac.crt');
const defaultCaKeyPath = path.join(msnDir, 'emac.key');
const defaultOutCertPath = path.join(msnDir, 'minisrv.crt');
const defaultOutKeyPath = path.join(msnDir, 'minisrv.key');
function parseArgs(argv) { function parseArgs(argv) {
const out = {}; const out = {};
@@ -31,42 +49,6 @@ function parseArgs(argv) {
return out; return out;
} }
function extractDomainsFromRedirectMap(text) {
const found = [];
const seen = new Set();
const re = /"([A-Za-z0-9.-]+\.)"\s*:\s*self\.redirect_ip/g;
let match;
while ((match = re.exec(text))) {
const clean = match[1].replace(/\.$/, '').toLowerCase();
if (!seen.has(clean)) {
seen.add(clean);
found.push(clean);
}
}
return found;
}
function loadDomains(args) {
if (args['from-map-file']) {
const mapText = fs.readFileSync(path.resolve(workspaceRoot, args['from-map-file']), 'utf8');
const domains = extractDomainsFromRedirectMap(mapText);
if (!domains.length) {
throw new Error('No domains were extracted from --from-map-file.');
}
return domains;
}
if (!fs.existsSync(domainsFile)) {
throw new Error('Domain file not found: ' + domainsFile);
}
const domains = fs.readFileSync(domainsFile, 'utf8')
.split(/\r?\n/)
.map((s) => s.trim().toLowerCase())
.filter((s) => s && !s.startsWith('#'));
return Array.from(new Set(domains));
}
function loadPemOrThrow(filePath, label) { function loadPemOrThrow(filePath, label) {
if (!fs.existsSync(filePath)) { if (!fs.existsSync(filePath)) {
@@ -99,9 +81,8 @@ function generateCert({ domains, caCertPem, caKeyPem, outCertPath, outKeyPath, y
cert.publicKey = keys.publicKey; cert.publicKey = keys.publicKey;
cert.serialNumber = forge.util.bytesToHex(forge.random.getBytesSync(16)); cert.serialNumber = forge.util.bytesToHex(forge.random.getBytesSync(16));
const now = new Date(); cert.validity.notBefore = new Date('2000-01-01T12:00:00Z');
cert.validity.notBefore = new Date(now.getTime() - 24 * 60 * 60 * 1000); cert.validity.notAfter = new Date('2099-12-31T23:59:59Z');
cert.validity.notAfter = new Date(now.getTime() + years * 365 * 24 * 60 * 60 * 1000);
const cn = domains[0] || 'headwaiter.trusted.msntv.msn.com'; const cn = domains[0] || 'headwaiter.trusted.msntv.msn.com';
cert.setSubject([ cert.setSubject([
@@ -141,7 +122,6 @@ function main() {
const years = Number(args.years || 15); const years = Number(args.years || 15);
const sig = String(args.sig || 'sha1'); const sig = String(args.sig || 'sha1');
const domains = loadDomains(args);
const caCertPem = loadPemOrThrow(caCertPath, 'CA cert'); const caCertPem = loadPemOrThrow(caCertPath, 'CA cert');
const caKeyPem = loadPemOrThrow(caKeyPath, 'CA key'); const caKeyPem = loadPemOrThrow(caKeyPath, 'CA key');

View File

@@ -0,0 +1,29 @@
-----BEGIN CERTIFICATE-----
MIIE8TCCA9mgAwIBAgIQoRyc0ioSEdkYGPQxwYXUDjANBgkqhkiG9w0BAQUFADB0
MRkwFwYDVQQDDBBtaW5pc3J2IHNlcnZpY2VzMREwDwYDVQQIDAhOZXcgWW9yazEL
MAkGA1UEBhMCVVMxHjAcBgkqhkiG9w0BCQEWD3plZmllQHplZmllLm5ldDEXMBUG
A1UECgwOWmVmaWUgTmV0d29ya3MwIBcNMDAwMTAxMTIwMDAwWhgPMjA5OTEyMzEy
MzU5NTlaMFExKTAnBgNVBAMTIGhlYWR3YWl0ZXIudHJ1c3RlZC5tc250di5tc24u
Y29tMRcwFQYDVQQKEw5aZWZpZSBOZXR3b3JrczELMAkGA1UEBhMCVVMwggEiMA0G
CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCbEGP/jBpuLj8aDzeIzcN+HWaorttU
WKqF5IY5s/nRm4WHdYUaKgDf9ksBJm2+igB0XSI1d06hvrI1VkMejvJzZpq+RNJe
TqMy7cp5zPcLLymUkb8a1ziY6ZjezHMLCRyfU2zyMR3yqHCAaYP6JtEZqc3Ht4o6
NfVCF4A8uynHZEGW47Iz4e6gLnzXutN7/ngDPw2hi/2XKN/E5djBg1yXHeQ+Y14n
Ab0sHO4DgsUOYruTZu/TyO1A2ewpsGC40cRsbPAkeHtViOqVBUegDTfiqxbGpxaQ
Qfdq/b8NYQmaRO/I/kH1IfYMiI+RxeBhUiBYlSEwOBNcJrc4etIzdYOVAgMBAAGj
ggGeMIIBmjAJBgNVHRMEAjAAMAsGA1UdDwQEAwIFoDATBgNVHSUEDDAKBggrBgEF
BQcDATCCAWkGA1UdEQSCAWAwggFcgiBoZWFkd2FpdGVyLnRydXN0ZWQubXNudHYu
bXNuLmNvbYIZc2cxLnRydXN0ZWQubXNudHYubXNuLmNvbYIZc2cyLnRydXN0ZWQu
bXNudHYubXNuLmNvbYIZc2czLnRydXN0ZWQubXNudHYubXNuLmNvbYIZc2c0LnRy
dXN0ZWQubXNudHYubXNuLmNvbYINbXNudHYubXNuLmNvbYIWbWFpbC5zZXJ2aWNl
cy5saXZlLmNvbYIObG9naW4ubGl2ZS5jb22CEXBvcHRpbWl6ZS5tc24uY29tghFm
YXZvcml0ZXMubXNuLmNvbYIRbWVzc2VuZ2VyLm1zbi5jb22CEWxpdmVmaWxlc3Rv
cmUuY29tghZ1c2Vycy5zdG9yYWdlLmxpdmUuY29tgglnLm1zbi5jb22CF21zbmlh
bG9naW4ucGFzc3BvcnQuY29tgg1taW5pc3J2LmxvY2FsMA0GCSqGSIb3DQEBBQUA
A4IBAQBCeWJbS8JJNlC6OIgUQue4bkDk5bNDUFuktAWXqmoM7Kt4MLDB1O60ez/T
9cSGqU9QicSn1eL0O7ETpEPO4tSDU0KB9GnulndWbyI/OgaVOC6ON7v4Uv0z7+nw
ninT66R+wajuSf9HyC8HA2FSMcIdiLBDtzUsSIxxG/EneEuxkBv31kQkHbWubIY4
9SJeIF5evjQZOC3KRVlfa9nwM3qon7RUfLWF1CZlRmaFfZQf2r0cH5cVt13kyxKI
gc8rwbGGOGsQFlw6GzGTUNQSv35xOCRNDvPtkp+dJ4GFMCbrg2OVk8n0Ecwu9jma
TSZ7B6w5w/1YzXyFlcZolxrxrupi
-----END CERTIFICATE-----

View File

@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAmxBj/4wabi4/Gg83iM3Dfh1mqK7bVFiqheSGObP50ZuFh3WF
GioA3/ZLASZtvooAdF0iNXdOob6yNVZDHo7yc2aavkTSXk6jMu3Kecz3Cy8plJG/
Gtc4mOmY3sxzCwkcn1Ns8jEd8qhwgGmD+ibRGanNx7eKOjX1QheAPLspx2RBluOy
M+HuoC5817rTe/54Az8NoYv9lyjfxOXYwYNclx3kPmNeJwG9LBzuA4LFDmK7k2bv
08jtQNnsKbBguNHEbGzwJHh7VYjqlQVHoA034qsWxqcWkEH3av2/DWEJmkTvyP5B
9SH2DIiPkcXgYVIgWJUhMDgTXCa3OHrSM3WDlQIDAQABAoIBABsMUiEgp3iaLQwm
5N1CZSwhxUc9zwjouYAHA4nbtMNFEVb2XywX8RSgAefWs/R1XoxttXqlj8wDd1L5
89XIAg8yseHoH9ju9yU6WOQDpoEnLiXaAX+VVKxJrQ6KE7Q0V++Lzhh2DF8IVi10
Gl8fd8B1/+zrPVuXj2tCra4KSMiclRBEvj0DjMlCHzR+JXDU8AVDg8KVi7ZQeKkx
kYEFzEwtzuOEOZQte7I1mqZ6Uns5tnpkcJISS9p8OzOK8+FvQY+w/Ya1g5bS8wXk
e56UtjZ33myUzwaVUEIBqSXEsAAFvnvvvv95LQPd0CRppOJWxGPcFTrW9Lsc38Ex
5k/0TOkCgYEAzaF/j6de4dq7r5WMk+ZYOWJpoT56SEJiEPhOCOZEA0eiBhKpwsE7
U/qOYaJdS8nR5XPz9H5fNn0cs+1f4Em6vUSHZHfSJ20ovwxH4Dgoa4MTJg7dRe4H
Bw9PQLSy8EIYYjjmbHbTNvdx947eW3SCqEE+hKPhuI0vHTYA19gLIckCgYEAwQv/
E8qNkXhJg5vCUrWXWyi1BOF5pHMLE2YY4S5744w5fy4rQ7Squrx22nE8pvSmNOs+
2ZMk9J2RKry6ZHG6tx8mlstHJFdkVUeYo4Dx6K5C0YH5+0j6VLEdnRPt5gCDhOAp
l/vmMK3vaPwm3sN9k1dvfS6Nu6HKf3dF+U7PmW0CgYEAx11xA6JOR8N+fLoN1cda
eiiEKSUeAbj6w2E6pz45asSkviaSGJSoJL+kE90Lf1NpAYHuYHm1bmrWzUuzzQ0u
1BoA+tOBGWCoRLJvbWCFL9ehVSDMP+SLQ7kAKcgIRRCP+4sXPMnn9j7qbA5EX38S
vnajWo5ZZkjcCQI2xw5bnKECgYBkxQBXsmoHv4SQYKQlTIk/mpYzgAdhYnQroiXm
lb8x3qa8zrri0tI0O+DG0klY1WhVQ19Bb9/gmMeISY/6kqtmn6ojGOWAAOZs5by1
zL96OEzE1FZLZ6LUxsewkRCj+SyuFd1gaquUSZcpdEZODjnkycV20PBHO4Na2kX6
h7syWQKBgQC8SNTXuawSYMH8KZk8WmxgejSz1t67CkZduhErKzci8iuR+WKtB24J
EvAWfMeWUTW5vGokHJrillrrWdw1J7QivcxXXZ48GNQKxbXFUY4KBB67v8wIu0lq
O+tlfmj14IJeXc4u1S/I5lSKnG+niIBYzqXeyxNWwd4H1zncP+dssg==
-----END RSA PRIVATE KEY-----

View File

@@ -0,0 +1,354 @@
#!/usr/bin/env node
'use strict';
const fs = require('fs');
const path = require('path');
const ROOT_DIR = path.resolve(__dirname, '..');
const DEFAULT_USER_CONFIG_PATH = path.join(ROOT_DIR, 'user_config.json');
const DEFAULT_BASE_CONFIG_PATH = path.join(ROOT_DIR, 'includes', 'config.json');
const DEPRECIATED_CONFIG_PATH = path.join(ROOT_DIR, 'includes', 'depreciated.json');
function printUsage() {
console.log('Usage: node tools/scan_service_vault_deprecations.js [options]');
console.log('');
console.log('Options:');
console.log(' --vault <path> Add a vault root to scan (can be repeated).');
console.log(' --config <path> Config file to read ServiceVaults from (default: user_config.json).');
console.log(' --base-config <path> Base config fallback (default: includes/config.json).');
console.log(' --ext <csv> File extensions to scan (default: .js). Example: --ext .js,.txt');
console.log(' --json Emit machine-readable JSON output.');
console.log(' --fail-on-found Exit with code 2 if any deprecations are found.');
console.log(' -h, --help Show this help text.');
}
function parseJsonWithComments(json) {
if (typeof json !== 'string') json = json ? json.toString() : '';
let result = '';
let i = 0;
let isString = false;
let isEscape = false;
let isBlockComment = false;
let isLineComment = false;
while (i < json.length) {
const ch = json[i];
const next = json[i + 1];
if (!isString && !isEscape && ch === '/' && next === '*') {
isBlockComment = true;
i += 1;
} else if (isBlockComment && ch === '*' && next === '/') {
isBlockComment = false;
i += 1;
} else if (!isString && !isEscape && ch === '/' && next === '/') {
isLineComment = true;
i += 1;
} else if (isLineComment && (ch === '\n' || ch === '\r')) {
isLineComment = false;
} else if (!isBlockComment && !isLineComment) {
if (ch === '"' && !isEscape) {
isString = !isString;
}
isEscape = ch === '\\' && !isEscape;
result += ch;
}
i += 1;
}
return JSON.parse(result);
}
function readConfigIfExists(filePath) {
if (!filePath || !fs.existsSync(filePath)) return null;
const raw = fs.readFileSync(filePath, 'utf8');
if (!raw.trim()) return {};
return parseJsonWithComments(raw);
}
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 || null,
replacement: entry.replacement || null
}));
return mapped.length > 0 ? mapped : {};
} catch (error) {
console.warn(`Warning: failed to load ${DEPRECIATED_CONFIG_PATH}: ${error.message}`);
return {};
}
}
function parseArgs(argv) {
const args = argv.slice(2);
const options = {
vaults: [],
configPath: DEFAULT_USER_CONFIG_PATH,
baseConfigPath: DEFAULT_BASE_CONFIG_PATH,
extensions: new Set(['.js']),
json: false,
failOnFound: false
};
for (let i = 0; i < args.length; i += 1) {
const arg = args[i];
if (arg === '-h' || arg === '--help') {
printUsage();
process.exit(0);
}
if (arg === '--vault') {
i += 1;
const value = args[i];
if (!value) throw new Error('Missing value for --vault');
options.vaults.push(value);
continue;
}
if (arg === '--config') {
i += 1;
const value = args[i];
if (!value) throw new Error('Missing value for --config');
options.configPath = path.isAbsolute(value) ? value : path.resolve(process.cwd(), value);
continue;
}
if (arg === '--base-config') {
i += 1;
const value = args[i];
if (!value) throw new Error('Missing value for --base-config');
options.baseConfigPath = path.isAbsolute(value) ? value : path.resolve(process.cwd(), value);
continue;
}
if (arg === '--ext') {
i += 1;
const value = args[i];
if (!value) throw new Error('Missing value for --ext');
options.extensions = new Set(
value
.split(',')
.map((ext) => ext.trim().toLowerCase())
.filter(Boolean)
.map((ext) => (ext.startsWith('.') ? ext : `.${ext}`))
);
continue;
}
if (arg === '--json') {
options.json = true;
continue;
}
if (arg === '--fail-on-found') {
options.failOnFound = true;
continue;
}
throw new Error(`Unknown argument: ${arg}`);
}
return options;
}
function getConfiguredVaults(configPath, baseConfigPath) {
const userConfig = readConfigIfExists(configPath) || {};
const baseConfig = readConfigIfExists(baseConfigPath) || {};
const userVaults = userConfig.config && Array.isArray(userConfig.config.ServiceVaults)
? userConfig.config.ServiceVaults
: null;
const baseVaults = baseConfig.config && Array.isArray(baseConfig.config.ServiceVaults)
? baseConfig.config.ServiceVaults
: null;
const selected = userVaults && userVaults.length > 0 ? userVaults : (baseVaults || []);
return selected.map((vault) => path.resolve(ROOT_DIR, String(vault)));
}
function walkFiles(rootDir, extensions, fileList = []) {
if (!fs.existsSync(rootDir)) return fileList;
const entries = fs.readdirSync(rootDir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(rootDir, entry.name);
if (entry.isDirectory()) {
walkFiles(fullPath, extensions, fileList);
continue;
}
if (!entry.isFile()) continue;
const ext = path.extname(entry.name).toLowerCase();
if (extensions.has(ext)) {
fileList.push(fullPath);
}
}
return fileList;
}
function toLineColumn(text, index) {
let line = 1;
let column = 1;
for (let i = 0; i < index; i += 1) {
if (text[i] === '\n') {
line += 1;
column = 1;
} else {
column += 1;
}
}
return { line, column };
}
function scanFile(filePath, patterns) {
const content = fs.readFileSync(filePath, 'utf8');
const findings = [];
for (const rule of patterns) {
rule.pattern.lastIndex = 0;
let match;
while ((match = rule.pattern.exec(content)) !== null) {
const loc = toLineColumn(content, match.index);
findings.push({
ruleId: rule.id,
match: match[0],
message: rule.message,
removeVersion: rule.removeVersion,
replacement: rule.replacement,
line: loc.line,
column: loc.column
});
}
}
return findings;
}
function formatRelative(targetPath) {
return path.relative(ROOT_DIR, targetPath) || targetPath;
}
function main() {
const options = parseArgs(process.argv);
const deprecationPatterns = loadDepreciatedPatterns();
if (deprecationPatterns.length === 0) {
console.warn('No deprecation patterns found. Exiting without scanning.');
process.exit(0);
}
const configuredVaults = getConfiguredVaults(options.configPath, options.baseConfigPath);
const explicitVaults = options.vaults.map((vault) => (
path.isAbsolute(vault) ? path.resolve(vault) : path.resolve(process.cwd(), vault)
));
const vaultsToScan = [...new Set([...configuredVaults, ...explicitVaults])];
if (vaultsToScan.length === 0) {
throw new Error('No ServiceVault paths found. Define config.ServiceVaults or pass --vault.');
}
const missingVaults = [];
const filesToScan = [];
for (const vaultPath of vaultsToScan) {
if (!fs.existsSync(vaultPath)) {
missingVaults.push(vaultPath);
continue;
}
walkFiles(vaultPath, options.extensions, filesToScan);
}
const results = [];
let totalFindings = 0;
for (const filePath of filesToScan) {
const findings = scanFile(filePath, deprecationPatterns);
if (findings.length > 0) {
totalFindings += findings.length;
results.push({ file: filePath, findings });
}
}
if (options.json) {
const payload = {
rootDir: ROOT_DIR,
scannedVaults: vaultsToScan,
missingVaults,
scannedFiles: filesToScan.length,
matchedFiles: results.length,
totalFindings,
results
};
console.log(JSON.stringify(payload, null, 2));
} else {
console.log('ServiceVault deprecation scan');
console.log('- Deprecation count: ' + deprecationPatterns.length);
console.log(`- Vault roots: ${vaultsToScan.length}`);
console.log(`- Missing vault roots: ${missingVaults.length}`);
console.log(`- Files scanned: ${filesToScan.length}`);
console.log(`- Files with deprecations: ${results.length}`);
console.log(`- Total deprecations: ${totalFindings}`);
if (missingVaults.length > 0) {
console.log('');
console.log('Missing vault roots:');
for (const missing of missingVaults) {
console.log(` - ${formatRelative(missing)}`);
}
}
if (results.length > 0) {
console.log('');
for (const result of results) {
console.log(formatRelative(result.file));
for (const finding of result.findings) {
console.log(` ${finding.line}:${finding.column} ${finding.ruleId}`);
console.log(` ${finding.message}`);
if (finding.removeVersion) {
console.log(` Remove version: ${finding.removeVersion}`);
}
console.log(` Match: ${finding.match}`);
if (finding.replacement) {
console.log(` Fix: ${finding.replacement}`);
}
}
}
}
}
if (totalFindings > 0 && options.failOnFound) {
process.exit(2);
}
}
try {
main();
} catch (error) {
console.error(`Error: ${error.message || error}`);
process.exit(1);
}