@fett/synology-api 0.0.1-beta.9 → 0.0.1
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/LICENSE +21 -0
- package/README.md +81 -10
- package/lib/cli/apis.js +47 -0
- package/lib/{esm/cli → cli}/config.js +38 -41
- package/lib/cli/core.js +20 -0
- package/lib/{types/cli → cli}/helper.d.ts +1 -0
- package/lib/{esm/cli → cli}/helper.js +9 -5
- package/lib/cli/index.d.ts +2 -0
- package/lib/cli/index.js +5 -0
- package/lib/{types/core.d.ts → core.d.ts} +1 -1
- package/lib/core.js +99 -0
- package/lib/decorators/bindMethods.js +45 -0
- package/lib/{types/errorcodes.d.ts → errorcodes.d.ts} +2 -2
- package/lib/{esm/errorcodes.js → errorcodes.js} +10 -5
- package/lib/helpers.js +70 -0
- package/lib/index.d.ts +4 -0
- package/lib/index.js +4 -0
- package/lib/{types/modules → modules}/Api/Auth.d.ts +2 -2
- package/lib/modules/Api/Auth.js +30 -0
- package/lib/modules/Api/Info.d.ts +3 -0
- package/lib/modules/Api/Info.js +15 -0
- package/lib/modules/Api/index.d.ts +2 -0
- package/lib/modules/Api/index.js +2 -0
- package/lib/modules/AudioStation/Album.d.ts +30 -0
- package/lib/modules/AudioStation/Album.js +17 -0
- package/lib/modules/AudioStation/Artist.d.ts +25 -0
- package/lib/modules/AudioStation/Artist.js +17 -0
- package/lib/modules/AudioStation/Cover.js +0 -0
- package/lib/modules/AudioStation/Folder.d.ts +24 -0
- package/lib/modules/AudioStation/Folder.js +17 -0
- package/lib/modules/AudioStation/Info.d.ts +45 -0
- package/lib/modules/AudioStation/Info.js +9 -0
- package/lib/modules/AudioStation/Lyrics.d.ts +25 -0
- package/lib/modules/AudioStation/Lyrics.js +23 -0
- package/lib/modules/AudioStation/Playlist.d.ts +34 -0
- package/lib/modules/AudioStation/Playlist.js +38 -0
- package/lib/modules/AudioStation/Search.d.ts +53 -0
- package/lib/modules/AudioStation/Search.js +17 -0
- package/lib/modules/AudioStation/Song.d.ts +33 -0
- package/lib/modules/AudioStation/Song.js +27 -0
- package/lib/modules/AudioStation/index.d.ts +23 -0
- package/lib/modules/AudioStation/index.js +23 -0
- package/lib/modules/Auth/AuthKey.d.ts +8 -0
- package/lib/modules/Auth/AuthKey.js +12 -0
- package/lib/modules/Auth/index.d.ts +7 -0
- package/lib/modules/Auth/index.js +8 -0
- package/lib/modules/FileStation/File.d.ts +0 -0
- package/lib/modules/FileStation/File.js +0 -0
- package/lib/{types/modules → modules}/FileStation/Info.d.ts +1 -1
- package/lib/modules/FileStation/Info.js +9 -0
- package/lib/{types/modules → modules}/FileStation/List.d.ts +1 -1
- package/lib/modules/FileStation/List.js +30 -0
- package/lib/{types/modules → modules}/FileStation/index.d.ts +2 -2
- package/lib/{esm/modules → modules}/FileStation/index.js +2 -2
- package/lib/modules/VideoStation/File.d.ts +48 -0
- package/lib/modules/VideoStation/File.js +20 -0
- package/lib/modules/VideoStation/HomeVideo.d.ts +6 -0
- package/lib/modules/VideoStation/HomeVideo.js +10 -0
- package/lib/modules/VideoStation/Info.d.ts +44 -0
- package/lib/modules/VideoStation/Info.js +9 -0
- package/lib/modules/VideoStation/Library.d.ts +70 -0
- package/lib/modules/VideoStation/Library.js +23 -0
- package/lib/modules/VideoStation/Movie.d.ts +27 -0
- package/lib/modules/VideoStation/Movie.js +13 -0
- package/lib/modules/VideoStation/Streaming.d.ts +11 -0
- package/lib/modules/VideoStation/Streaming.js +24 -0
- package/lib/modules/VideoStation/index.d.ts +15 -0
- package/lib/modules/VideoStation/index.js +16 -0
- package/lib/modules/VideoStation/types.d.ts +27 -0
- package/lib/modules/VideoStation/types.js +1 -0
- package/lib/modules/index.d.ts +26 -0
- package/lib/{esm/modules → modules}/index.js +31 -3
- package/lib/types/{types/apiInfo.d.ts → apiInfo.d.ts} +15 -2
- package/lib/{esm/types → types}/apiInfo.js +15 -1
- package/lib/types/index.d.ts +2 -5
- package/lib/types/index.js +2 -0
- package/lib/{types/utils.d.ts → utils.d.ts} +1 -0
- package/lib/{esm/utils.js → utils.js} +3 -0
- package/package.json +23 -24
- package/bin/syno +0 -6
- package/lib/cjs/index.js +0 -728
- package/lib/esm/cli/apis.js +0 -53
- package/lib/esm/cli/index.js +0 -30
- package/lib/esm/core.js +0 -111
- package/lib/esm/helpers.js +0 -81
- package/lib/esm/index.js +0 -5
- package/lib/esm/modules/Api/Auth.js +0 -43
- package/lib/esm/modules/Api/Info.js +0 -26
- package/lib/esm/modules/Api/index.js +0 -2
- package/lib/esm/modules/AudioStation/Song.js +0 -18
- package/lib/esm/modules/AudioStation/index.js +0 -7
- package/lib/esm/modules/FileStation/Info.js +0 -20
- package/lib/esm/modules/FileStation/List.js +0 -32
- package/lib/esm/types/index.js +0 -2
- package/lib/types/modules/Api/Info.d.ts +0 -3
- package/lib/types/modules/Api/index.d.ts +0 -2
- package/lib/types/modules/AudioStation/Song.d.ts +0 -22
- package/lib/types/modules/AudioStation/index.d.ts +0 -7
- package/lib/types/modules/index.d.ts +0 -16
- package/lib/types/types/index.d.ts +0 -2
- /package/lib/{types/cli → cli}/apis.d.ts +0 -0
- /package/lib/{types/cli → cli}/config.d.ts +0 -0
- /package/lib/{types/cli/index.d.ts → cli/core.d.ts} +0 -0
- /package/lib/{types/constants.d.ts → constants.d.ts} +0 -0
- /package/lib/{esm/constants.js → constants.js} +0 -0
- /package/lib/{esm/modules/FileStation/File.js → decorators/bindMethods.d.ts} +0 -0
- /package/lib/{types/helpers.d.ts → helpers.d.ts} +0 -0
- /package/lib/{types/modules/FileStation/File.d.ts → modules/AudioStation/Cover.d.ts} +0 -0
- /package/lib/types/{types/request.d.ts → request.d.ts} +0 -0
- /package/lib/{esm/types → types}/request.js +0 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Chris Song
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,26 +1,97 @@
|
|
|
1
1
|
# Synology Api
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
⚠️ The project is under development ...
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[中文文档](./README-zh_CN.md)
|
|
6
|
+
Synology Api Node.js wrapper, can be used in Browser、CLI or Nodejs to interact with Synology NAS.
|
|
7
|
+
You can use domain or ip address, also supports Synology Quick Connect connect Synology server.
|
|
8
|
+
All apis from [https://kb.synology.cn](https://kb.synology.cn/zh-cn/search?query=API&services%5B%5D=File_Station)
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
npm install @fett/synology-api
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Use In Browser
|
|
17
|
+
|
|
18
|
+
First you need to confirm that you can access across domains,for example in the React Native environment
|
|
19
|
+
|
|
20
|
+
```js
|
|
21
|
+
import { SynologyApi } from "@fett/synology-api/browser";
|
|
22
|
+
// Create a new instance
|
|
23
|
+
const api = new SynologyApi({
|
|
24
|
+
server: "https://192.168.1.1:5001",
|
|
25
|
+
username: "username",
|
|
26
|
+
password: "password",
|
|
27
|
+
});
|
|
28
|
+
// you can now use the api by calling the methods
|
|
29
|
+
const info = await api.fs.getInfo();
|
|
30
|
+
console.log(info);
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Use In Node.js
|
|
6
34
|
|
|
7
35
|
```js
|
|
8
36
|
import SynologyApi from '@fett/synology-api';
|
|
9
37
|
|
|
10
|
-
// Create a new instance
|
|
11
38
|
const synologyApi = new SynologyApi(
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
39
|
+
server: "https://192.168.1.1:5001",
|
|
40
|
+
username: "username",
|
|
41
|
+
password: "password",
|
|
15
42
|
);
|
|
16
43
|
|
|
17
|
-
// you can now use the api by calling the methods
|
|
18
44
|
const info = await synologyApi.FileStation.getInfo();
|
|
45
|
+
console.log(info);
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Use In CLI
|
|
19
49
|
|
|
50
|
+
First install the package globally
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npm i -g @fett/synology-api
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Then run cmd help message
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
syno --help
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
run `syno config -h` you will see the help message
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
Usage: synology config [options] [command]
|
|
66
|
+
|
|
67
|
+
synology api config management
|
|
68
|
+
|
|
69
|
+
Options:
|
|
70
|
+
-h, --help display help for command
|
|
71
|
+
|
|
72
|
+
Commands:
|
|
73
|
+
ls List all the connection config
|
|
74
|
+
add [options] [name] Add connection config
|
|
75
|
+
use [name] Change current connection
|
|
76
|
+
del [name] Remove a connection
|
|
77
|
+
rename <name> <newName> Change connection name
|
|
78
|
+
help [command] display help for command
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
add a connect configuration
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
syno config add newConnetionName --server=https://192.168.1.1:5001 --username=admin --password=password
|
|
20
85
|
```
|
|
21
86
|
|
|
22
|
-
|
|
87
|
+
then you can use it and exec command
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
syno config use newConnetionName
|
|
23
91
|
|
|
24
|
-
|
|
92
|
+
syno fs getInfo --pretty # print file system info
|
|
93
|
+
|
|
94
|
+
```
|
|
25
95
|
|
|
26
|
-
|
|
96
|
+
## License
|
|
97
|
+
[MIT](https://github.com/ChrisSong1994/synology-api/blob/main/LICENSE)
|
package/lib/cli/apis.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { program } from "commander";
|
|
2
|
+
import { loadConfig } from "./config.js";
|
|
3
|
+
import { SynologyApi } from "../core.js";
|
|
4
|
+
import { SynologyApiKeys } from "../modules/index.js";
|
|
5
|
+
export const onMethodsCall = (module) => async (method, options) => {
|
|
6
|
+
const config = await loadConfig();
|
|
7
|
+
const params = JSON.parse(options.params || "{}");
|
|
8
|
+
const pretty = options.pretty;
|
|
9
|
+
const synologyApi = new SynologyApi(config.connections[config.used]);
|
|
10
|
+
if (synologyApi[module]?.[method]) {
|
|
11
|
+
const result = await synologyApi[module]?.[method](params);
|
|
12
|
+
if (pretty) {
|
|
13
|
+
console.log(JSON.stringify(result, null, 2));
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
console.log(JSON.stringify(result));
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
synologyApi.disconnect();
|
|
20
|
+
process.exit(0);
|
|
21
|
+
};
|
|
22
|
+
// register all modules
|
|
23
|
+
const apisCmdRegisterInfo = [
|
|
24
|
+
{
|
|
25
|
+
cmd: SynologyApiKeys.FileStation,
|
|
26
|
+
alias: SynologyApiKeys.fs,
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
cmd: SynologyApiKeys.AudioStation,
|
|
30
|
+
alias: SynologyApiKeys.as,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
cmd: SynologyApiKeys.VideoStation,
|
|
34
|
+
alias: SynologyApiKeys.vs,
|
|
35
|
+
},
|
|
36
|
+
];
|
|
37
|
+
export const apiCmdRegister = () => {
|
|
38
|
+
apisCmdRegisterInfo.forEach((info) => {
|
|
39
|
+
program
|
|
40
|
+
.command(`${info.cmd} [methods]`)
|
|
41
|
+
.alias(info.alias)
|
|
42
|
+
.option("-p,--params <params>", `${info.cmd} methods params`)
|
|
43
|
+
.option("--pretty", "Prettyprint JSON Output")
|
|
44
|
+
.description(`Synology ${info.cmd} method call`)
|
|
45
|
+
.action(onMethodsCall(info.cmd));
|
|
46
|
+
});
|
|
47
|
+
};
|
|
@@ -1,47 +1,38 @@
|
|
|
1
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
-
});
|
|
9
|
-
};
|
|
10
1
|
import fse from "fs-extra";
|
|
11
2
|
import path from "path";
|
|
12
3
|
import os from "os";
|
|
13
4
|
import { program } from "commander";
|
|
14
5
|
import chalk from "chalk";
|
|
15
|
-
import { isLowerCaseEqual, printMessages, geneDashLine } from "./helper";
|
|
6
|
+
import { isLowerCaseEqual, printMessages, geneDashLine } from "./helper.js";
|
|
16
7
|
const CONFIG_FILE_PATH = process.env.NODE_ENV === "development"
|
|
17
8
|
? path.join(process.cwd(), "./.userdata/.synology-api.json") // development
|
|
18
9
|
: path.join(os.homedir(), "./.synology-api.json");
|
|
19
10
|
// load config
|
|
20
|
-
export const loadConfig = () =>
|
|
21
|
-
if (!(
|
|
22
|
-
|
|
23
|
-
|
|
11
|
+
export const loadConfig = async () => {
|
|
12
|
+
if (!(await fse.pathExists(CONFIG_FILE_PATH))) {
|
|
13
|
+
await fse.ensureFile(CONFIG_FILE_PATH);
|
|
14
|
+
await fse.writeJSON(CONFIG_FILE_PATH, {
|
|
24
15
|
used: "",
|
|
25
16
|
connections: {},
|
|
26
17
|
});
|
|
27
18
|
}
|
|
28
|
-
const config =
|
|
19
|
+
const config = await fse.readJSON(CONFIG_FILE_PATH);
|
|
29
20
|
return config;
|
|
30
|
-
}
|
|
21
|
+
};
|
|
31
22
|
// update config
|
|
32
|
-
export const updateConfig = (config) =>
|
|
33
|
-
|
|
34
|
-
}
|
|
23
|
+
export const updateConfig = async (config) => {
|
|
24
|
+
await fse.writeJSON(CONFIG_FILE_PATH, config);
|
|
25
|
+
};
|
|
35
26
|
// check config
|
|
36
|
-
export const checkConfig = () =>
|
|
27
|
+
export const checkConfig = async () => { };
|
|
37
28
|
export const configCmdRegister = () => {
|
|
38
29
|
const configCmd = program.command("config").description("synology api config management");
|
|
39
30
|
// list connection config
|
|
40
31
|
configCmd
|
|
41
32
|
.command("ls")
|
|
42
33
|
.description("List all the connection config")
|
|
43
|
-
.action(() =>
|
|
44
|
-
const config =
|
|
34
|
+
.action(async () => {
|
|
35
|
+
const config = await loadConfig();
|
|
45
36
|
const keys = Object.keys(config.connections);
|
|
46
37
|
const dashLineLength = Math.max(...keys.map((key) => key.length)) + 3;
|
|
47
38
|
const messages = keys.map((key) => {
|
|
@@ -57,7 +48,7 @@ export const configCmdRegister = () => {
|
|
|
57
48
|
connection.password);
|
|
58
49
|
});
|
|
59
50
|
printMessages(messages);
|
|
60
|
-
})
|
|
51
|
+
});
|
|
61
52
|
// add connection
|
|
62
53
|
configCmd
|
|
63
54
|
.command("add [name]")
|
|
@@ -65,24 +56,30 @@ export const configCmdRegister = () => {
|
|
|
65
56
|
.requiredOption("-s, --server <server>", "Synology server domain or QuickConnect ID ")
|
|
66
57
|
.requiredOption("-u, --username <username>", "username")
|
|
67
58
|
.requiredOption("-p, --password <password>", "password")
|
|
68
|
-
.action((name, options) =>
|
|
59
|
+
.action(async (name, options) => {
|
|
69
60
|
if (!name.trim())
|
|
70
61
|
throw new Error("Plaease input connection name");
|
|
71
62
|
// 实际代码中应保存配置到文件
|
|
72
|
-
const config =
|
|
73
|
-
const newConfig =
|
|
63
|
+
const config = await loadConfig();
|
|
64
|
+
const newConfig = {
|
|
65
|
+
...config,
|
|
66
|
+
connections: {
|
|
67
|
+
...config.connections,
|
|
68
|
+
[name]: {
|
|
74
69
|
server: options.server,
|
|
75
70
|
username: options.username,
|
|
76
71
|
password: options.password,
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
await updateConfig(newConfig);
|
|
76
|
+
});
|
|
80
77
|
// use connection
|
|
81
78
|
configCmd
|
|
82
79
|
.command("use [name]")
|
|
83
80
|
.description("Change current connection")
|
|
84
|
-
.action((name) =>
|
|
85
|
-
const config =
|
|
81
|
+
.action(async (name) => {
|
|
82
|
+
const config = await loadConfig();
|
|
86
83
|
if (config.used === name)
|
|
87
84
|
return;
|
|
88
85
|
if (!config.connections[name]) {
|
|
@@ -90,15 +87,15 @@ export const configCmdRegister = () => {
|
|
|
90
87
|
return;
|
|
91
88
|
}
|
|
92
89
|
config.used = name;
|
|
93
|
-
|
|
94
|
-
})
|
|
90
|
+
await updateConfig(config);
|
|
91
|
+
});
|
|
95
92
|
// del connection
|
|
96
93
|
configCmd
|
|
97
94
|
.command("del [name]")
|
|
98
95
|
.description("Remove a connection")
|
|
99
|
-
.action((name) =>
|
|
96
|
+
.action(async (name) => {
|
|
100
97
|
console.log("Remove a connection", name);
|
|
101
|
-
const config =
|
|
98
|
+
const config = await loadConfig();
|
|
102
99
|
// 删除配置
|
|
103
100
|
if (!config.connections[name]) {
|
|
104
101
|
console.log("Connection not found");
|
|
@@ -108,14 +105,14 @@ export const configCmdRegister = () => {
|
|
|
108
105
|
if (config.used === name) {
|
|
109
106
|
config.used = "";
|
|
110
107
|
}
|
|
111
|
-
|
|
112
|
-
})
|
|
108
|
+
await updateConfig(config);
|
|
109
|
+
});
|
|
113
110
|
// Change connection name
|
|
114
111
|
configCmd
|
|
115
112
|
.command("rename <name> <newName>")
|
|
116
113
|
.description("Change connection name")
|
|
117
|
-
.action((name, newName) =>
|
|
118
|
-
const config =
|
|
114
|
+
.action(async (name, newName) => {
|
|
115
|
+
const config = await loadConfig();
|
|
119
116
|
if (!config.connections[name]) {
|
|
120
117
|
console.log("Connection not found");
|
|
121
118
|
return;
|
|
@@ -125,6 +122,6 @@ export const configCmdRegister = () => {
|
|
|
125
122
|
if (config.used === name) {
|
|
126
123
|
config.used = newName;
|
|
127
124
|
}
|
|
128
|
-
|
|
129
|
-
})
|
|
125
|
+
await updateConfig(config);
|
|
126
|
+
});
|
|
130
127
|
};
|
package/lib/cli/core.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import fse from "fs-extra";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { program } from "commander";
|
|
4
|
+
import { configCmdRegister } from "./config.js";
|
|
5
|
+
import { apiCmdRegister } from "./apis.js";
|
|
6
|
+
import { __dirname } from "./helper.js";
|
|
7
|
+
export async function loadCli() {
|
|
8
|
+
// git program info
|
|
9
|
+
const pkg = await fse.readJSON(path.join(__dirname(), "../../package.json"));
|
|
10
|
+
// command
|
|
11
|
+
program
|
|
12
|
+
.name("synology")
|
|
13
|
+
.usage("<command> [options]")
|
|
14
|
+
.description("synology api cli tool")
|
|
15
|
+
.version(pkg.version, "-v, --version", "output the version number");
|
|
16
|
+
// register commands
|
|
17
|
+
configCmdRegister();
|
|
18
|
+
apiCmdRegister();
|
|
19
|
+
program.parse(process.argv);
|
|
20
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export declare const __dirname: () => string;
|
|
1
2
|
export declare function printMessages(messages: string[]): void;
|
|
2
3
|
export declare function isLowerCaseEqual(str1: string, str2: string): boolean;
|
|
3
4
|
export declare function padding(message?: string, before?: number, after?: number): string;
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
|
+
import { fileURLToPath } from "url";
|
|
3
|
+
import { dirname } from "path";
|
|
4
|
+
export const __dirname = () => {
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
return dirname(__filename);
|
|
7
|
+
};
|
|
2
8
|
export function printMessages(messages) {
|
|
3
9
|
console.log(messages.join("\n"));
|
|
4
10
|
}
|
|
@@ -8,12 +14,10 @@ export function isLowerCaseEqual(str1, str2) {
|
|
|
8
14
|
}
|
|
9
15
|
return !str1 && !str2;
|
|
10
16
|
}
|
|
11
|
-
export function padding(message =
|
|
12
|
-
return
|
|
13
|
-
message +
|
|
14
|
-
new Array(after).fill(' ').join(''));
|
|
17
|
+
export function padding(message = "", before = 1, after = 1) {
|
|
18
|
+
return new Array(before).fill(" ").join("") + message + new Array(after).fill(" ").join("");
|
|
15
19
|
}
|
|
16
20
|
export function geneDashLine(message, length) {
|
|
17
|
-
const finalMessage = new Array(Math.max(2, length - message.length + 2)).join(
|
|
21
|
+
const finalMessage = new Array(Math.max(2, length - message.length + 2)).join("-");
|
|
18
22
|
return padding(chalk.dim(finalMessage));
|
|
19
23
|
}
|
package/lib/cli/index.js
ADDED
package/lib/core.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// reference: https://kb.synology.com/zh-tw/DSM/tutorial/What_websites_does_Synology_NAS_connect_to_when_running_services_or_updating_software
|
|
2
|
+
import ky from "ky";
|
|
3
|
+
import { BaseSynologyApi } from "./modules/index.js";
|
|
4
|
+
import { isHttpUrl, getApiKey, isUndfined } from "./utils.js";
|
|
5
|
+
import { getServerInfo } from "./helpers.js";
|
|
6
|
+
import { login, logout, getApiInfo } from "./modules/Api/index.js";
|
|
7
|
+
import { resWithErrorCode } from "./errorcodes.js";
|
|
8
|
+
export class SynologyApi extends BaseSynologyApi {
|
|
9
|
+
constructor(options) {
|
|
10
|
+
super();
|
|
11
|
+
this.isConnecting = false;
|
|
12
|
+
this.authInfo = null;
|
|
13
|
+
this.apiInfo = {};
|
|
14
|
+
this.server = options.server;
|
|
15
|
+
this.username = options.username;
|
|
16
|
+
this.password = options.password;
|
|
17
|
+
this.baseUrl = `${this.server}/webapi/`;
|
|
18
|
+
}
|
|
19
|
+
async connect() {
|
|
20
|
+
// if quickconnect id
|
|
21
|
+
if (!isHttpUrl(this.server)) {
|
|
22
|
+
this.server = await getServerInfo(this.server);
|
|
23
|
+
// reset base url
|
|
24
|
+
this.baseUrl = `${this.server}/webapi/`;
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
const result = await login(this);
|
|
28
|
+
this.authInfo = result.data;
|
|
29
|
+
this.isConnecting = true;
|
|
30
|
+
await this._getApiInfo();
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
console.error(err);
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async disconnect() {
|
|
39
|
+
try {
|
|
40
|
+
await logout(this);
|
|
41
|
+
this.authInfo = null;
|
|
42
|
+
this.apiInfo = {};
|
|
43
|
+
this.isConnecting = false;
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
console.error(err);
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async _getApiInfo() {
|
|
52
|
+
try {
|
|
53
|
+
const result = await getApiInfo(this);
|
|
54
|
+
this.apiInfo = result.data;
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
console.error(err);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
hasApi(apiName) {
|
|
61
|
+
if (!this.isConnecting) {
|
|
62
|
+
throw new Error("Not connected");
|
|
63
|
+
}
|
|
64
|
+
return Object.prototype.hasOwnProperty.call(this.apiInfo, apiName);
|
|
65
|
+
}
|
|
66
|
+
async run(apiName, options) {
|
|
67
|
+
if (!this.isConnecting) {
|
|
68
|
+
const res = await this.connect();
|
|
69
|
+
if (!res) {
|
|
70
|
+
throw new Error("Not connected");
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (!this.hasApi(apiName)) {
|
|
74
|
+
throw new Error(`${apiName} not found`);
|
|
75
|
+
}
|
|
76
|
+
const { method = "get", params, data, headers } = options;
|
|
77
|
+
const api = this.apiInfo[apiName];
|
|
78
|
+
const url = `${this.baseUrl}${api.path}`;
|
|
79
|
+
const externalParams = {
|
|
80
|
+
api: apiName,
|
|
81
|
+
version: api.maxVersion,
|
|
82
|
+
_sid: this.authInfo.sid,
|
|
83
|
+
...params,
|
|
84
|
+
};
|
|
85
|
+
let result = null;
|
|
86
|
+
if (method === "get") {
|
|
87
|
+
result = await ky.get(url, { searchParams: externalParams, headers }).json();
|
|
88
|
+
}
|
|
89
|
+
if (method === "post") {
|
|
90
|
+
result = await ky.post(url, { searchParams: externalParams, json: data, headers }).json();
|
|
91
|
+
}
|
|
92
|
+
// match error code msg
|
|
93
|
+
const apiKey = getApiKey(apiName);
|
|
94
|
+
if (!isUndfined(apiKey)) {
|
|
95
|
+
result = resWithErrorCode(apiKey, result);
|
|
96
|
+
}
|
|
97
|
+
return result;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// export type Method = (...args: any[]) => Promise<any>;
|
|
2
|
+
// type MethodKeys<T> = {
|
|
3
|
+
// [K in keyof T]: {
|
|
4
|
+
// [M in keyof T[K]]: Method;
|
|
5
|
+
// };
|
|
6
|
+
// }[keyof T];
|
|
7
|
+
// /**
|
|
8
|
+
// * 将源对象的方法绑定到目标类的原型上,并确保方法的 this 指向类实例
|
|
9
|
+
// * @param source 包含要绑定方法的对象
|
|
10
|
+
// */
|
|
11
|
+
// export function BindMethods<T extends object>(source: T): ClassDecorator {
|
|
12
|
+
// return function (target: { prototype: any }) {
|
|
13
|
+
// // 获取源对象的所有方法键
|
|
14
|
+
// const methodKeys = Object.getOwnPropertyNames(source).filter(
|
|
15
|
+
// (key): key is MethodKeys<T> => typeof source[key] === "function"
|
|
16
|
+
// );
|
|
17
|
+
// // 为每个方法创建绑定版本并定义到原型上
|
|
18
|
+
// methodKeys.forEach((methodName) => {
|
|
19
|
+
// const originalMethod = source[methodName] as Function;
|
|
20
|
+
// // 创建绑定方法的描述符
|
|
21
|
+
// const descriptor: PropertyDescriptor = {
|
|
22
|
+
// configurable: true,
|
|
23
|
+
// enumerable: false, // 使方法不可枚举
|
|
24
|
+
// // 使用 getter 确保每个实例都有自己的绑定函数
|
|
25
|
+
// get() {
|
|
26
|
+
// // 跳过原型访问,直接返回原始方法
|
|
27
|
+
// if (this === target.prototype || this === undefined) {
|
|
28
|
+
// return originalMethod;
|
|
29
|
+
// }
|
|
30
|
+
// // 为实例创建绑定方法并缓存
|
|
31
|
+
// const boundMethod = originalMethod.bind(this);
|
|
32
|
+
// Object.defineProperty(this, methodName, {
|
|
33
|
+
// value: boundMethod,
|
|
34
|
+
// configurable: true,
|
|
35
|
+
// writable: true,
|
|
36
|
+
// enumerable: false,
|
|
37
|
+
// });
|
|
38
|
+
// return boundMethod;
|
|
39
|
+
// },
|
|
40
|
+
// };
|
|
41
|
+
// // 将绑定方法定义到类原型上
|
|
42
|
+
// Object.defineProperty(target.prototype, methodName, descriptor);
|
|
43
|
+
// });
|
|
44
|
+
// };
|
|
45
|
+
// }
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { SynologyApiKeys } from "./modules";
|
|
2
|
-
import { SynologyApiResponse } from "./types";
|
|
1
|
+
import { SynologyApiKeys } from "./modules/index.js";
|
|
2
|
+
import { SynologyApiResponse } from "./types/index.js";
|
|
3
3
|
export declare const SYNOLOGY_ERROR_CODES: {
|
|
4
4
|
[SynologyApiKeys.FileStation]: {
|
|
5
5
|
400: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { SynologyApiKeys } from "./modules";
|
|
2
|
-
import { isUndfined } from "./utils";
|
|
1
|
+
import { SynologyApiKeys } from "./modules/index.js";
|
|
2
|
+
import { isUndfined } from "./utils.js";
|
|
3
3
|
const CODE_SUCCESS = 0;
|
|
4
4
|
const CODE_UNKNOWN = 9999;
|
|
5
5
|
export const SYNOLOGY_ERROR_CODES = {
|
|
@@ -88,10 +88,15 @@ export const SYNOLOGY_ERROR_CODES = {
|
|
|
88
88
|
},
|
|
89
89
|
};
|
|
90
90
|
export const resWithErrorCode = (apiKey, res) => {
|
|
91
|
-
var _a, _b;
|
|
92
91
|
const errorCodes = SYNOLOGY_ERROR_CODES[apiKey];
|
|
93
|
-
const code =
|
|
92
|
+
const code = res?.error?.code;
|
|
94
93
|
if (isUndfined(code))
|
|
95
94
|
return res;
|
|
96
|
-
return
|
|
95
|
+
return {
|
|
96
|
+
...res,
|
|
97
|
+
error: {
|
|
98
|
+
...res.error,
|
|
99
|
+
message: res?.error?.message || errorCodes?.[code] || "Unknown error",
|
|
100
|
+
},
|
|
101
|
+
};
|
|
97
102
|
};
|
package/lib/helpers.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import ky from "ky";
|
|
2
|
+
import { GLOBAL_QUICK_CONNECT_URL, QUICK_CONNECT_PINGPANG_API } from "./constants.js";
|
|
3
|
+
const getServersFromServerInfo = async (serverInfo) => {
|
|
4
|
+
// WAN IP
|
|
5
|
+
if (serverInfo?.server?.external?.ip) {
|
|
6
|
+
const server = `http://${serverInfo.server.external.ip}:${serverInfo.service.port}`;
|
|
7
|
+
if (await pingpang(server)) {
|
|
8
|
+
return server;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
// proxy server
|
|
12
|
+
if (serverInfo?.service?.relay_ip) {
|
|
13
|
+
const server = `http://${serverInfo.service.relay_ip}:${serverInfo.service.relay_port}`;
|
|
14
|
+
const res = await pingpang(server);
|
|
15
|
+
if (res) {
|
|
16
|
+
return server;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
// lan ip
|
|
20
|
+
if (serverInfo?.server?.interface?.[0]) {
|
|
21
|
+
const server = `http://${serverInfo.server.interface?.[0].ip}:${serverInfo.service.port}`;
|
|
22
|
+
if (await pingpang(server)) {
|
|
23
|
+
return server;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
export const getServerInfo = async (quickConnectId) => {
|
|
28
|
+
const params = {
|
|
29
|
+
version: 1,
|
|
30
|
+
id: "dsm",
|
|
31
|
+
serverID: quickConnectId,
|
|
32
|
+
get_ca_fingerprints: true,
|
|
33
|
+
command: "get_server_info",
|
|
34
|
+
};
|
|
35
|
+
const serverInfo = await ky.post(GLOBAL_QUICK_CONNECT_URL, { json: params }).json();
|
|
36
|
+
if (!serverInfo?.service?.relay_ip && !serverInfo?.service?.relay_port) {
|
|
37
|
+
const relayRequestParams = {
|
|
38
|
+
version: 1,
|
|
39
|
+
id: "dsm",
|
|
40
|
+
serverID: quickConnectId,
|
|
41
|
+
platform: "web",
|
|
42
|
+
command: "request_tunnel",
|
|
43
|
+
};
|
|
44
|
+
const result = await ky
|
|
45
|
+
.post(`https://${serverInfo.env.control_host}/Serv.php`, { json: relayRequestParams })
|
|
46
|
+
.json();
|
|
47
|
+
return getServersFromServerInfo(result);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
return getServersFromServerInfo(serverInfo);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
// pingpang
|
|
54
|
+
export const pingpang = async (server) => {
|
|
55
|
+
try {
|
|
56
|
+
const result = await ky
|
|
57
|
+
.get(`${server}/${QUICK_CONNECT_PINGPANG_API}`)
|
|
58
|
+
.json();
|
|
59
|
+
if (result.success) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
66
|
+
}
|
|
67
|
+
catch (_err) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
};
|
package/lib/index.d.ts
ADDED
package/lib/index.js
ADDED