@quenty/nevermore-cli-helpers 1.8.1 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -47
- package/dist/auth/cookie/cookie-parser.d.ts +7 -0
- package/dist/auth/cookie/cookie-parser.d.ts.map +1 -0
- package/dist/auth/cookie/cookie-parser.js +18 -0
- package/dist/auth/cookie/cookie-parser.js.map +1 -0
- package/dist/auth/cookie/cookie-parser.test.d.ts +2 -0
- package/dist/auth/cookie/cookie-parser.test.d.ts.map +1 -0
- package/dist/auth/cookie/cookie-parser.test.js +32 -0
- package/dist/auth/cookie/cookie-parser.test.js.map +1 -0
- package/dist/auth/cookie/index.d.ts +41 -0
- package/dist/auth/cookie/index.d.ts.map +1 -0
- package/dist/auth/cookie/index.js +188 -0
- package/dist/auth/cookie/index.js.map +1 -0
- package/dist/auth/cookie/linux.d.ts +14 -0
- package/dist/auth/cookie/linux.d.ts.map +1 -0
- package/dist/auth/cookie/linux.js +147 -0
- package/dist/auth/cookie/linux.js.map +1 -0
- package/dist/auth/cookie/macos.d.ts +2 -0
- package/dist/auth/cookie/macos.d.ts.map +1 -0
- package/dist/auth/cookie/macos.js +66 -0
- package/dist/auth/cookie/macos.js.map +1 -0
- package/dist/auth/cookie/validate-cookie.test.d.ts +2 -0
- package/dist/auth/cookie/validate-cookie.test.d.ts.map +1 -0
- package/dist/auth/cookie/validate-cookie.test.js +27 -0
- package/dist/auth/cookie/validate-cookie.test.js.map +1 -0
- package/dist/auth/cookie/windows.d.ts +2 -0
- package/dist/auth/cookie/windows.d.ts.map +1 -0
- package/dist/auth/cookie/windows.js +76 -0
- package/dist/auth/cookie/windows.js.map +1 -0
- package/dist/auth/open-cloud/credential-store.d.ts +14 -0
- package/dist/auth/open-cloud/credential-store.d.ts.map +1 -0
- package/dist/auth/open-cloud/credential-store.js +108 -0
- package/dist/auth/open-cloud/credential-store.js.map +1 -0
- package/dist/utils.d.ts +5 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +3 -0
- package/dist/utils.js.map +1 -1
- package/dist/version-checker.d.ts +2 -0
- package/dist/version-checker.d.ts.map +1 -1
- package/dist/version-checker.js +22 -20
- package/dist/version-checker.js.map +1 -1
- package/package.json +8 -4
- package/src/auth/cookie/cookie-parser.test.ts +38 -0
- package/src/auth/cookie/cookie-parser.ts +18 -0
- package/src/auth/cookie/index.ts +271 -0
- package/src/auth/cookie/linux.ts +175 -0
- package/src/auth/cookie/macos.ts +88 -0
- package/src/auth/cookie/validate-cookie.test.ts +39 -0
- package/src/auth/cookie/windows.ts +96 -0
- package/src/auth/open-cloud/credential-store.ts +149 -0
- package/src/utils.ts +25 -0
- package/src/version-checker.ts +28 -24
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,104 +3,67 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
# [1.10.0](https://github.com/Quenty/Nevermore/compare/@quenty/nevermore-cli-helpers@1.9.0...@quenty/nevermore-cli-helpers@1.10.0) (2026-05-18)
|
|
7
7
|
|
|
8
8
|
**Note:** Version bump only for package @quenty/nevermore-cli-helpers
|
|
9
9
|
|
|
10
|
+
# [1.9.0](https://github.com/Quenty/Nevermore/compare/@quenty/nevermore-cli-helpers@1.8.1...@quenty/nevermore-cli-helpers@1.9.0) (2026-05-14)
|
|
10
11
|
|
|
12
|
+
### Features
|
|
11
13
|
|
|
14
|
+
- **studio-bridge:** persistent sessions and Linux/Wine support ([#669](https://github.com/Quenty/Nevermore/issues/669)) ([51514c9](https://github.com/Quenty/Nevermore/commit/51514c90cfe438ad1217dabb7fd64fd7350097a9))
|
|
12
15
|
|
|
13
|
-
|
|
14
|
-
# [1.8.0](https://github.com/Quenty/Nevermore/compare/@quenty/nevermore-cli-helpers@1.7.0...@quenty/nevermore-cli-helpers@1.8.0) (2026-02-23)
|
|
16
|
+
## [1.8.1](https://github.com/Quenty/Nevermore/compare/@quenty/nevermore-cli-helpers@1.8.0...@quenty/nevermore-cli-helpers@1.8.1) (2026-04-27)
|
|
15
17
|
|
|
16
18
|
**Note:** Version bump only for package @quenty/nevermore-cli-helpers
|
|
17
19
|
|
|
20
|
+
# [1.8.0](https://github.com/Quenty/Nevermore/compare/@quenty/nevermore-cli-helpers@1.7.0...@quenty/nevermore-cli-helpers@1.8.0) (2026-02-23)
|
|
18
21
|
|
|
19
|
-
|
|
20
|
-
|
|
22
|
+
**Note:** Version bump only for package @quenty/nevermore-cli-helpers
|
|
21
23
|
|
|
22
24
|
# [1.7.0](https://github.com/Quenty/Nevermore/compare/@quenty/nevermore-cli-helpers@1.6.0...@quenty/nevermore-cli-helpers@1.7.0) (2026-02-19)
|
|
23
25
|
|
|
24
26
|
**Note:** Version bump only for package @quenty/nevermore-cli-helpers
|
|
25
27
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
28
|
# [1.6.0](https://github.com/Quenty/Nevermore/compare/@quenty/nevermore-cli-helpers@1.5.0...@quenty/nevermore-cli-helpers@1.6.0) (2026-02-18)
|
|
31
29
|
|
|
32
30
|
**Note:** Version bump only for package @quenty/nevermore-cli-helpers
|
|
33
31
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
32
|
# [1.5.0](https://github.com/Quenty/Nevermore/compare/@quenty/nevermore-cli-helpers@1.4.0...@quenty/nevermore-cli-helpers@1.5.0) (2026-02-18)
|
|
39
33
|
|
|
40
34
|
**Note:** Version bump only for package @quenty/nevermore-cli-helpers
|
|
41
35
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
36
|
# [1.4.0](https://github.com/Quenty/Nevermore/compare/@quenty/nevermore-cli-helpers@1.3.2...@quenty/nevermore-cli-helpers@1.4.0) (2026-02-17)
|
|
47
37
|
|
|
48
38
|
**Note:** Version bump only for package @quenty/nevermore-cli-helpers
|
|
49
39
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
40
|
## [1.3.2](https://github.com/Quenty/Nevermore/compare/@quenty/nevermore-cli-helpers@1.3.1...@quenty/nevermore-cli-helpers@1.3.2) (2026-02-17)
|
|
55
41
|
|
|
56
42
|
**Note:** Version bump only for package @quenty/nevermore-cli-helpers
|
|
57
43
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
44
|
## [1.3.1](https://github.com/Quenty/Nevermore/compare/@quenty/nevermore-cli-helpers@1.3.0...@quenty/nevermore-cli-helpers@1.3.1) (2026-02-17)
|
|
63
45
|
|
|
64
46
|
**Note:** Version bump only for package @quenty/nevermore-cli-helpers
|
|
65
47
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
48
|
# [1.3.0](https://github.com/Quenty/Nevermore/compare/@quenty/nevermore-cli-helpers@1.2.0...@quenty/nevermore-cli-helpers@1.3.0) (2026-02-17)
|
|
71
49
|
|
|
72
50
|
**Note:** Version bump only for package @quenty/nevermore-cli-helpers
|
|
73
51
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
52
|
# [1.2.0](https://github.com/Quenty/Nevermore/compare/@quenty/nevermore-cli-helpers@1.1.0...@quenty/nevermore-cli-helpers@1.2.0) (2026-01-14)
|
|
79
53
|
|
|
80
|
-
|
|
81
54
|
### Features
|
|
82
55
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
56
|
+
- Nevermore CLI also warns if you're in a local development mode, and checks for updates quicker ([98a24d2](https://github.com/Quenty/Nevermore/commit/98a24d25d50249f2a95c2ec5164074ca7393cd02))
|
|
88
57
|
|
|
89
58
|
# 1.1.0 (2026-01-14)
|
|
90
59
|
|
|
91
|
-
|
|
92
60
|
### Bug Fixes
|
|
93
61
|
|
|
94
|
-
|
|
95
|
-
|
|
62
|
+
- Record current version in yargs ([9512418](https://github.com/Quenty/Nevermore/commit/9512418354e5275f96cc757547dd9b5973a93859))
|
|
96
63
|
|
|
97
64
|
### Features
|
|
98
65
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
66
|
+
- Nevermore CLI now checks to make sure it is up to date ([d86930c](https://github.com/Quenty/Nevermore/commit/d86930c85f24d879dee9ce5ce413488d1250246d))
|
|
104
67
|
|
|
105
68
|
# v1.1.0 (Wed Jan 14 2026)
|
|
106
69
|
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const COOKIE_NAME = ".ROBLOSECURITY";
|
|
2
|
+
/**
|
|
3
|
+
* Parse a Studio cookie value that may be in COOK::<cookie> format.
|
|
4
|
+
* Matches Mantle's parse_roblox_studio_cookie.
|
|
5
|
+
*/
|
|
6
|
+
export declare function parseStudioCookieValue(value: string): string | undefined;
|
|
7
|
+
//# sourceMappingURL=cookie-parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cookie-parser.d.ts","sourceRoot":"","sources":["../../../src/auth/cookie/cookie-parser.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,WAAW,mBAAmB,CAAC;AAE5C;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAWxE"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const COOKIE_NAME = '.ROBLOSECURITY';
|
|
2
|
+
/**
|
|
3
|
+
* Parse a Studio cookie value that may be in COOK::<cookie> format.
|
|
4
|
+
* Matches Mantle's parse_roblox_studio_cookie.
|
|
5
|
+
*/
|
|
6
|
+
export function parseStudioCookieValue(value) {
|
|
7
|
+
for (const item of value.split(',')) {
|
|
8
|
+
const parts = item.split('::');
|
|
9
|
+
if (parts.length === 2 && parts[0] === 'COOK') {
|
|
10
|
+
const cookie = parts[1];
|
|
11
|
+
if (cookie.startsWith('<') && cookie.endsWith('>')) {
|
|
12
|
+
return cookie.slice(1, -1);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=cookie-parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cookie-parser.js","sourceRoot":"","sources":["../../../src/auth/cookie/cookie-parser.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,WAAW,GAAG,gBAAgB,CAAC;AAE5C;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CAAC,KAAa;IAClD,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QACpC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC/B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,MAAM,EAAE,CAAC;YAC9C,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;YACxB,IAAI,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;gBACnD,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;YAC7B,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cookie-parser.test.d.ts","sourceRoot":"","sources":["../../../src/auth/cookie/cookie-parser.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { COOKIE_NAME, parseStudioCookieValue } from './cookie-parser.js';
|
|
3
|
+
describe('COOKIE_NAME', () => {
|
|
4
|
+
it('equals .ROBLOSECURITY', () => {
|
|
5
|
+
expect(COOKIE_NAME).toBe('.ROBLOSECURITY');
|
|
6
|
+
});
|
|
7
|
+
});
|
|
8
|
+
describe('parseStudioCookieValue', () => {
|
|
9
|
+
it('parses COOK::<value> format with angle brackets', () => {
|
|
10
|
+
const result = parseStudioCookieValue('COOK::<abc123>');
|
|
11
|
+
expect(result).toBe('abc123');
|
|
12
|
+
});
|
|
13
|
+
it('parses value from comma-separated list', () => {
|
|
14
|
+
const result = parseStudioCookieValue('OTHER::stuff,COOK::<secret>');
|
|
15
|
+
expect(result).toBe('secret');
|
|
16
|
+
});
|
|
17
|
+
it('returns undefined for plain text', () => {
|
|
18
|
+
expect(parseStudioCookieValue('just a string')).toBeUndefined();
|
|
19
|
+
});
|
|
20
|
+
it('returns undefined for COOK:: without angle brackets', () => {
|
|
21
|
+
expect(parseStudioCookieValue('COOK::noBrackets')).toBeUndefined();
|
|
22
|
+
});
|
|
23
|
+
it('returns undefined for empty string', () => {
|
|
24
|
+
expect(parseStudioCookieValue('')).toBeUndefined();
|
|
25
|
+
});
|
|
26
|
+
it('handles a realistic cookie value', () => {
|
|
27
|
+
const cookie = '_|WARNING:-DO-NOT-SHARE|_abc123def456';
|
|
28
|
+
const result = parseStudioCookieValue(`COOK::<${cookie}>`);
|
|
29
|
+
expect(result).toBe(cookie);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
//# sourceMappingURL=cookie-parser.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cookie-parser.test.js","sourceRoot":"","sources":["../../../src/auth/cookie/cookie-parser.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAEzE,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;IAC3B,EAAE,CAAC,uBAAuB,EAAE,GAAG,EAAE;QAC/B,MAAM,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;IAC7C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,wBAAwB,EAAE,GAAG,EAAE;IACtC,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;QACzD,MAAM,MAAM,GAAG,sBAAsB,CAAC,gBAAgB,CAAC,CAAC;QACxD,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAChC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;QAChD,MAAM,MAAM,GAAG,sBAAsB,CAAC,6BAA6B,CAAC,CAAC;QACrE,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAChC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;QAC1C,MAAM,CAAC,sBAAsB,CAAC,eAAe,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;IAClE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC7D,MAAM,CAAC,sBAAsB,CAAC,kBAAkB,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;IACrE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC5C,MAAM,CAAC,sBAAsB,CAAC,EAAE,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;IACrD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;QAC1C,MAAM,MAAM,GAAG,uCAAuC,CAAC;QACvD,MAAM,MAAM,GAAG,sBAAsB,CAAC,UAAU,MAAM,GAAG,CAAC,CAAC;QAC3D,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC9B,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cookie auth and place creation for Roblox legacy APIs.
|
|
3
|
+
* Based on Mantle: https://github.com/blake-mealey/mantle
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Resolve the .ROBLOSECURITY cookie for legacy Roblox API calls.
|
|
7
|
+
*
|
|
8
|
+
* Resolution order (matching Mantle's rbx_cookie crate):
|
|
9
|
+
* 1. ROBLOSECURITY environment variable
|
|
10
|
+
* 2. Platform credential store (Windows Credential Manager / macOS HTTPStorages / Wine Credential Manager)
|
|
11
|
+
* 3. Platform legacy store (Windows Registry / macOS plist)
|
|
12
|
+
* 4. Interactive prompt
|
|
13
|
+
*/
|
|
14
|
+
export declare function getRobloxCookieAsync(): Promise<string>;
|
|
15
|
+
/**
|
|
16
|
+
* Create a new place in a universe using the legacy cookie-authenticated API.
|
|
17
|
+
* Returns the new place ID.
|
|
18
|
+
*/
|
|
19
|
+
export declare function createPlaceInUniverseAsync(cookie: string, universeId: number, placeName: string): Promise<number>;
|
|
20
|
+
export interface CookieValidationResult {
|
|
21
|
+
valid: boolean;
|
|
22
|
+
reason?: 'invalid' | 'network_error';
|
|
23
|
+
status?: number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Validates the ROBLOSECURITY cookie against the Roblox API.
|
|
27
|
+
* Returns a result indicating whether the cookie is valid.
|
|
28
|
+
* Network errors are treated as "unknown" (not invalid) so callers
|
|
29
|
+
* can decide whether to continue in offline scenarios.
|
|
30
|
+
*/
|
|
31
|
+
export declare function validateCookieAsync(cookie: string): Promise<CookieValidationResult>;
|
|
32
|
+
export interface RenamePlaceResult {
|
|
33
|
+
success: boolean;
|
|
34
|
+
reason?: 'no_cookie' | 'api_error';
|
|
35
|
+
status?: number;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Try to rename an existing place via the develop.roblox.com API.
|
|
39
|
+
*/
|
|
40
|
+
export declare function tryRenamePlaceAsync(placeId: number, placeName: string): Promise<RenamePlaceResult>;
|
|
41
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/auth/cookie/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AASH;;;;;;;;GAQG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,MAAM,CAAC,CA6B5D;AAuFD;;;GAGG;AACH,wBAAsB,0BAA0B,CAC9C,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,CAAC,CAkDjB;AAED,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,SAAS,GAAG,eAAe,CAAC;IACrC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,sBAAsB,CAAC,CAmBjC;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,WAAW,GAAG,WAAW,CAAC;IACnC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,iBAAiB,CAAC,CA0B5B"}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cookie auth and place creation for Roblox legacy APIs.
|
|
3
|
+
* Based on Mantle: https://github.com/blake-mealey/mantle
|
|
4
|
+
*/
|
|
5
|
+
import inquirer from 'inquirer';
|
|
6
|
+
import { OutputHelper } from '@quenty/cli-output-helpers';
|
|
7
|
+
import { COOKIE_NAME } from './cookie-parser.js';
|
|
8
|
+
import { readCookie as readWindowsCookie } from './windows.js';
|
|
9
|
+
import { readCookie as readMacOSCookie } from './macos.js';
|
|
10
|
+
import { readCookie as readLinuxCookie } from './linux.js';
|
|
11
|
+
/**
|
|
12
|
+
* Resolve the .ROBLOSECURITY cookie for legacy Roblox API calls.
|
|
13
|
+
*
|
|
14
|
+
* Resolution order (matching Mantle's rbx_cookie crate):
|
|
15
|
+
* 1. ROBLOSECURITY environment variable
|
|
16
|
+
* 2. Platform credential store (Windows Credential Manager / macOS HTTPStorages / Wine Credential Manager)
|
|
17
|
+
* 3. Platform legacy store (Windows Registry / macOS plist)
|
|
18
|
+
* 4. Interactive prompt
|
|
19
|
+
*/
|
|
20
|
+
export async function getRobloxCookieAsync() {
|
|
21
|
+
const envCookie = process.env.ROBLOSECURITY;
|
|
22
|
+
if (envCookie) {
|
|
23
|
+
return envCookie;
|
|
24
|
+
}
|
|
25
|
+
const platformCookie = readPlatformCookie();
|
|
26
|
+
if (platformCookie) {
|
|
27
|
+
return platformCookie;
|
|
28
|
+
}
|
|
29
|
+
// No interactive prompt in non-TTY environments (CI)
|
|
30
|
+
if (!process.stdin.isTTY) {
|
|
31
|
+
throw new Error('No .ROBLOSECURITY cookie available (set ROBLOSECURITY env var for CI)');
|
|
32
|
+
}
|
|
33
|
+
const { cookie } = await inquirer.prompt([
|
|
34
|
+
{
|
|
35
|
+
type: 'password',
|
|
36
|
+
name: 'cookie',
|
|
37
|
+
message: 'Enter your .ROBLOSECURITY cookie (from browser or Studio):',
|
|
38
|
+
mask: '*',
|
|
39
|
+
validate: (input) => input.length > 0 || 'Cookie cannot be empty',
|
|
40
|
+
},
|
|
41
|
+
]);
|
|
42
|
+
return cookie;
|
|
43
|
+
}
|
|
44
|
+
function readPlatformCookie() {
|
|
45
|
+
switch (process.platform) {
|
|
46
|
+
case 'win32':
|
|
47
|
+
return readWindowsCookie();
|
|
48
|
+
case 'darwin':
|
|
49
|
+
return readMacOSCookie();
|
|
50
|
+
case 'linux':
|
|
51
|
+
return readLinuxCookie();
|
|
52
|
+
default:
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Extract a rotated .ROBLOSECURITY cookie from a response's set-cookie header.
|
|
58
|
+
*/
|
|
59
|
+
function extractRotatedCookie(response) {
|
|
60
|
+
const setCookie = response.headers.get('set-cookie');
|
|
61
|
+
if (!setCookie) {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
// set-cookie may contain multiple cookies separated by commas (or multiple headers).
|
|
65
|
+
// Look for .ROBLOSECURITY=<value>.
|
|
66
|
+
const match = setCookie.match(/\.ROBLOSECURITY=([^;,\s]+)/);
|
|
67
|
+
return match?.[1];
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Make a cookie-authenticated request to Roblox, handling CSRF token exchange,
|
|
71
|
+
* cookie rotation capture, and 429 rate-limit retries.
|
|
72
|
+
*/
|
|
73
|
+
async function fetchWithCsrfAsync(url, cookie, options = {}) {
|
|
74
|
+
const headers = {
|
|
75
|
+
Cookie: `${COOKIE_NAME}=${cookie}`,
|
|
76
|
+
'User-Agent': 'Roblox/WinInet',
|
|
77
|
+
...options.headers,
|
|
78
|
+
};
|
|
79
|
+
let response = await fetch(url, {
|
|
80
|
+
...options,
|
|
81
|
+
headers,
|
|
82
|
+
});
|
|
83
|
+
if (response.status === 403) {
|
|
84
|
+
const csrfToken = response.headers.get('x-csrf-token');
|
|
85
|
+
if (csrfToken) {
|
|
86
|
+
headers['X-CSRF-TOKEN'] = csrfToken;
|
|
87
|
+
response = await fetch(url, {
|
|
88
|
+
...options,
|
|
89
|
+
headers,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// Retry once on rate limit
|
|
94
|
+
if (response.status === 429) {
|
|
95
|
+
const retryAfter = response.headers.get('retry-after');
|
|
96
|
+
const delaySec = retryAfter
|
|
97
|
+
? Math.min(parseInt(retryAfter, 10) || 2, 30)
|
|
98
|
+
: 2;
|
|
99
|
+
OutputHelper.verbose(`Rate limited (429). Retrying after ${delaySec}s...`);
|
|
100
|
+
await new Promise((resolve) => setTimeout(resolve, delaySec * 1000));
|
|
101
|
+
response = await fetch(url, { ...options, headers });
|
|
102
|
+
}
|
|
103
|
+
const rotatedCookie = extractRotatedCookie(response);
|
|
104
|
+
if (rotatedCookie) {
|
|
105
|
+
OutputHelper.verbose('Captured rotated .ROBLOSECURITY cookie from response.');
|
|
106
|
+
}
|
|
107
|
+
return { response, rotatedCookie };
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Create a new place in a universe using the legacy cookie-authenticated API.
|
|
111
|
+
* Returns the new place ID.
|
|
112
|
+
*/
|
|
113
|
+
export async function createPlaceInUniverseAsync(cookie, universeId, placeName) {
|
|
114
|
+
OutputHelper.verbose(`Creating place "${placeName}" in universe ${universeId}...`);
|
|
115
|
+
const createResult = await fetchWithCsrfAsync(`https://apis.roblox.com/universes/v1/user/universes/${universeId}/places`, cookie, {
|
|
116
|
+
method: 'POST',
|
|
117
|
+
headers: {
|
|
118
|
+
'Content-Type': 'application/json',
|
|
119
|
+
},
|
|
120
|
+
body: JSON.stringify({ templatePlaceId: 95206881 }),
|
|
121
|
+
});
|
|
122
|
+
if (!createResult.response.ok) {
|
|
123
|
+
const text = await createResult.response.text();
|
|
124
|
+
throw new Error(`Failed to create place: ${createResult.response.status} ${createResult.response.statusText}: ${text}`);
|
|
125
|
+
}
|
|
126
|
+
const createData = (await createResult.response.json());
|
|
127
|
+
const placeId = createData.placeId;
|
|
128
|
+
// Use rotated cookie if the create request triggered rotation
|
|
129
|
+
const { response: renameResponse } = await fetchWithCsrfAsync(`https://develop.roblox.com/v2/places/${placeId}`, createResult.rotatedCookie ?? cookie, {
|
|
130
|
+
method: 'PATCH',
|
|
131
|
+
headers: {
|
|
132
|
+
'Content-Type': 'application/json',
|
|
133
|
+
},
|
|
134
|
+
body: JSON.stringify({ name: placeName }),
|
|
135
|
+
});
|
|
136
|
+
if (!renameResponse.ok) {
|
|
137
|
+
OutputHelper.warn(`Place created (${placeId}) but rename failed — you can rename it manually.`);
|
|
138
|
+
}
|
|
139
|
+
OutputHelper.verbose(`Created place "${placeName}" — ID: ${placeId}`);
|
|
140
|
+
return placeId;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Validates the ROBLOSECURITY cookie against the Roblox API.
|
|
144
|
+
* Returns a result indicating whether the cookie is valid.
|
|
145
|
+
* Network errors are treated as "unknown" (not invalid) so callers
|
|
146
|
+
* can decide whether to continue in offline scenarios.
|
|
147
|
+
*/
|
|
148
|
+
export async function validateCookieAsync(cookie) {
|
|
149
|
+
try {
|
|
150
|
+
const response = await fetch('https://users.roblox.com/v1/users/authenticated', {
|
|
151
|
+
headers: {
|
|
152
|
+
Cookie: `.ROBLOSECURITY=${cookie}`,
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
if (response.status !== 200) {
|
|
156
|
+
return { valid: false, reason: 'invalid', status: response.status };
|
|
157
|
+
}
|
|
158
|
+
return { valid: true };
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
return { valid: false, reason: 'network_error' };
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Try to rename an existing place via the develop.roblox.com API.
|
|
166
|
+
*/
|
|
167
|
+
export async function tryRenamePlaceAsync(placeId, placeName) {
|
|
168
|
+
let cookie;
|
|
169
|
+
try {
|
|
170
|
+
cookie = await getRobloxCookieAsync();
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
return { success: false, reason: 'no_cookie' };
|
|
174
|
+
}
|
|
175
|
+
const { response } = await fetchWithCsrfAsync(`https://develop.roblox.com/v2/places/${placeId}`, cookie, {
|
|
176
|
+
method: 'PATCH',
|
|
177
|
+
headers: {
|
|
178
|
+
'Content-Type': 'application/json',
|
|
179
|
+
},
|
|
180
|
+
body: JSON.stringify({ name: placeName }),
|
|
181
|
+
});
|
|
182
|
+
if (response.ok) {
|
|
183
|
+
OutputHelper.verbose(`Renamed place ${placeId} to "${placeName}"`);
|
|
184
|
+
return { success: true };
|
|
185
|
+
}
|
|
186
|
+
return { success: false, reason: 'api_error', status: response.status };
|
|
187
|
+
}
|
|
188
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/auth/cookie/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read .ROBLOSECURITY from Wine's Credential Manager stored in the Wine
|
|
3
|
+
* registry file ($WINEPREFIX/user.reg).
|
|
4
|
+
*
|
|
5
|
+
* Wine stores Windows Credential Manager entries as registry keys under
|
|
6
|
+
* [Software\\Wine\\Credential Manager]. Each credential target becomes a
|
|
7
|
+
* subkey with hex-encoded blob values.
|
|
8
|
+
*
|
|
9
|
+
* Resolution order (mirrors windows.ts):
|
|
10
|
+
* 1. Modern: user-specific credential (RobloxStudioAuth.ROBLOSECURITY{userId})
|
|
11
|
+
* 2. Legacy: RobloxStudioAuth.ROBLOSECURITY (no user suffix)
|
|
12
|
+
*/
|
|
13
|
+
export declare function readCookie(): string | undefined;
|
|
14
|
+
//# sourceMappingURL=linux.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"linux.d.ts","sourceRoot":"","sources":["../../../src/auth/cookie/linux.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,IAAI,MAAM,GAAG,SAAS,CA2C/C"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import * as os from 'os';
|
|
4
|
+
import { OutputHelper } from '@quenty/cli-output-helpers';
|
|
5
|
+
import { COOKIE_NAME } from './cookie-parser.js';
|
|
6
|
+
/**
|
|
7
|
+
* Read .ROBLOSECURITY from Wine's Credential Manager stored in the Wine
|
|
8
|
+
* registry file ($WINEPREFIX/user.reg).
|
|
9
|
+
*
|
|
10
|
+
* Wine stores Windows Credential Manager entries as registry keys under
|
|
11
|
+
* [Software\\Wine\\Credential Manager]. Each credential target becomes a
|
|
12
|
+
* subkey with hex-encoded blob values.
|
|
13
|
+
*
|
|
14
|
+
* Resolution order (mirrors windows.ts):
|
|
15
|
+
* 1. Modern: user-specific credential (RobloxStudioAuth.ROBLOSECURITY{userId})
|
|
16
|
+
* 2. Legacy: RobloxStudioAuth.ROBLOSECURITY (no user suffix)
|
|
17
|
+
*/
|
|
18
|
+
export function readCookie() {
|
|
19
|
+
const userReg = getWineUserRegPath();
|
|
20
|
+
if (!userReg || !fs.existsSync(userReg)) {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
let regContent;
|
|
24
|
+
try {
|
|
25
|
+
regContent = fs.readFileSync(userReg, 'utf-8');
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
const credentials = parseWineCredentials(regContent);
|
|
31
|
+
// Modern: user-specific credential
|
|
32
|
+
const userId = credentials.get('https://www.roblox.com:RobloxStudioAuthuserid');
|
|
33
|
+
if (userId) {
|
|
34
|
+
const cookie = credentials.get(`https://www.roblox.com:RobloxStudioAuth${COOKIE_NAME}${userId}`);
|
|
35
|
+
if (cookie) {
|
|
36
|
+
OutputHelper.verbose(`Loaded cookie from Wine Credential Manager (user ${userId}).`);
|
|
37
|
+
return cookie;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// Legacy: no user suffix
|
|
41
|
+
const legacyCookie = credentials.get(`https://www.roblox.com:RobloxStudioAuth${COOKIE_NAME}`);
|
|
42
|
+
if (legacyCookie) {
|
|
43
|
+
OutputHelper.verbose('Loaded cookie from Wine Credential Manager (legacy).');
|
|
44
|
+
return legacyCookie;
|
|
45
|
+
}
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
function getWineUserRegPath() {
|
|
49
|
+
const wineprefix = process.env.WINEPREFIX || path.join(os.homedir(), '.wine');
|
|
50
|
+
return path.join(wineprefix, 'user.reg');
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Parse Wine's user.reg file for Credential Manager entries.
|
|
54
|
+
*
|
|
55
|
+
* Wine stores credentials under registry keys like:
|
|
56
|
+
* [Software\\Wine\\Credential Manager]
|
|
57
|
+
*
|
|
58
|
+
* Each credential is a named value where the name is the target and the
|
|
59
|
+
* value is a hex-encoded binary blob. The credential blob (the actual
|
|
60
|
+
* secret) is stored as UTF-8 bytes within the binary structure.
|
|
61
|
+
*
|
|
62
|
+
* Returns a Map of target name -> credential value (decoded string).
|
|
63
|
+
*/
|
|
64
|
+
function parseWineCredentials(regContent) {
|
|
65
|
+
const credentials = new Map();
|
|
66
|
+
// Wine Credential Manager stores creds as individual hex blobs under
|
|
67
|
+
// [Software\\Wine\\Credential Manager]. The key format is:
|
|
68
|
+
// "Target Name"=hex:xx,xx,xx,...
|
|
69
|
+
// The hex blob is a serialized CREDENTIAL struct.
|
|
70
|
+
const credSectionMatch = regContent.match(/\[Software\\\\Wine\\\\Credential Manager\]([\s\S]*?)(?=\n\[|$)/i);
|
|
71
|
+
if (!credSectionMatch) {
|
|
72
|
+
return credentials;
|
|
73
|
+
}
|
|
74
|
+
const section = credSectionMatch[1];
|
|
75
|
+
// Match each credential entry: "TargetName"=hex:bytes
|
|
76
|
+
const entryRegex = /^"(.+?)"=hex:(.+)$/gm;
|
|
77
|
+
let match;
|
|
78
|
+
while ((match = entryRegex.exec(section)) !== null) {
|
|
79
|
+
const targetName = unescapeRegString(match[1]);
|
|
80
|
+
const hexStr = match[2].replace(/\\\n\s*/g, '').replace(/,/g, '');
|
|
81
|
+
try {
|
|
82
|
+
const blob = Buffer.from(hexStr, 'hex');
|
|
83
|
+
const value = extractCredentialBlob(blob);
|
|
84
|
+
if (value) {
|
|
85
|
+
credentials.set(targetName, value);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// Malformed hex data
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return credentials;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Extract the credential value from a Wine serialized CREDENTIAL blob.
|
|
96
|
+
*
|
|
97
|
+
* The blob layout follows the Windows CREDENTIAL struct. The credential
|
|
98
|
+
* value (CredentialBlob) is stored as UTF-8 bytes. We look for the
|
|
99
|
+
* actual cookie/value content by searching for known patterns.
|
|
100
|
+
*/
|
|
101
|
+
function extractCredentialBlob(blob) {
|
|
102
|
+
// Wine's serialized credential format stores the blob data inline.
|
|
103
|
+
// The simplest approach: the credential value for Roblox entries is
|
|
104
|
+
// plain UTF-8 text. Try to find it by looking for cookie-like content
|
|
105
|
+
// or numeric user IDs.
|
|
106
|
+
// Try interpreting the entire blob as UTF-8 and looking for the value
|
|
107
|
+
const text = blob.toString('utf-8');
|
|
108
|
+
// For simple values (user IDs, cookie names), the blob may just be
|
|
109
|
+
// the raw UTF-8 string
|
|
110
|
+
if (text && isPrintableAscii(text)) {
|
|
111
|
+
return text;
|
|
112
|
+
}
|
|
113
|
+
// For structured blobs, search for the credential data section.
|
|
114
|
+
// Wine writes a serialized struct — scan for the longest printable
|
|
115
|
+
// ASCII substring that looks like a credential value.
|
|
116
|
+
let best = '';
|
|
117
|
+
let current = '';
|
|
118
|
+
for (let i = 0; i < blob.length; i++) {
|
|
119
|
+
const byte = blob[i];
|
|
120
|
+
if (byte >= 0x20 && byte < 0x7f) {
|
|
121
|
+
current += String.fromCharCode(byte);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
if (current.length > best.length) {
|
|
125
|
+
best = current;
|
|
126
|
+
}
|
|
127
|
+
current = '';
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (current.length > best.length) {
|
|
131
|
+
best = current;
|
|
132
|
+
}
|
|
133
|
+
return best.length > 0 ? best : undefined;
|
|
134
|
+
}
|
|
135
|
+
function isPrintableAscii(str) {
|
|
136
|
+
for (let i = 0; i < str.length; i++) {
|
|
137
|
+
const code = str.charCodeAt(i);
|
|
138
|
+
if (code < 0x20 || code > 0x7e) {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return str.length > 0;
|
|
143
|
+
}
|
|
144
|
+
function unescapeRegString(str) {
|
|
145
|
+
return str.replace(/\\\\/g, '\\');
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=linux.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"linux.js","sourceRoot":"","sources":["../../../src/auth/cookie/linux.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"macos.d.ts","sourceRoot":"","sources":["../../../src/auth/cookie/macos.ts"],"names":[],"mappings":"AAqFA,wBAAgB,UAAU,IAAI,MAAM,GAAG,SAAS,CAE/C"}
|