dsh-calendar 0.9.1 → 0.9.3
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 +11 -0
- package/README.en.md +217 -222
- package/README.md +251 -258
- package/lib/client.js +12 -8
- package/lib/oauth.js +43 -3
- package/lib/web.js +5 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.9.3(2026-10-01)
|
|
4
|
+
|
|
5
|
+
- 修复 Google 切换到 iCloud、Nextcloud 或自建 CalDAV 时仍继承 OAuth 认证方式的问题;测试连接和保存使用新服务商的认证方式,原服务商的已有凭据继续保留。
|
|
6
|
+
- 连接摘要识别环境变量中的密码、client secret 和 refresh token,输入框仅提示“已配置”,不回传或复制环境密钥;纯空白值不算已配置。
|
|
7
|
+
- 修改连接草稿后清除过时的测试结论,避免把旧账号的失败或成功显示成新草稿的结果。
|
|
8
|
+
- 4 项新回归先复现后修复,178 项 Windows 测试通过;验证宿主为官方源码构建的 Harness 0.2.0-rc.2。
|
|
9
|
+
|
|
10
|
+
## 0.9.2(2026-09-29)
|
|
11
|
+
|
|
12
|
+
- 修复:OAuth 刷新失败改为**按错误码给出可操作指引**(授权失效 / 应用配置错误 / 权限范围不足 / 限流或服务端错误 / 需重新验证),并在诊断信息里带上 OAuth 标识与 HTTP 状态。
|
|
13
|
+
- 安全:错误信息**只接受标准错误标识符**(长度与字符集受限),绝不回显 `error_description` 等自由文本,避免把 client secret 或 refresh token 带进日志与界面。
|
|
3
14
|
## 0.9.1(2026-09-28)
|
|
4
15
|
|
|
5
16
|
- 兼容验证更新到 Harness 0.2.0-rc.1:173 项测试与 18 插件共同加载检查通过(96 工具 / 35 技能注册)。
|
package/README.en.md
CHANGED
|
@@ -1,222 +1,217 @@
|
|
|
1
|
-
# dsh-calendar
|
|
2
|
-
|
|
3
|
-
## 0.9.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
Validation host: Harness `0.2.0-rc.
|
|
8
|
-
|
|
9
|
-
   
|
|
10
|
-
|
|
11
|
-
[](https://awesome-dsh-plugin.com)
|
|
12
|
-
|
|
13
|
-
DSH community plugin: read/write calendar events via CalDAV. Provides 5 calendar tools (calendar_list / calendar_create / calendar_update / calendar_delete / calendar_search) plus the offline `calendar_health` configuration check. Google uses OAuth 2.0; iCloud / Nextcloud / custom servers retain Basic authentication by default. Includes visual week/month views, drag and keyboard rescheduling, connection settings and connection tests. Profile cordis.patch.yml configuration is also supported. Updates and deletes fetch the known event directly, avoiding a collection-wide index request.
|
|
14
|
-
|
|
15
|
-
## Compatibility
|
|
16
|
-
|
|
17
|
-
Version **0.9.0** (2026-09-23) follows the settings interface introduced in Harness **0.1.7**: the host removed `ctx.settings.register`, so configuration now lives in the entry's own `Config`. Every connection field is declared `volatile` (panel edits apply live, no restart) and the three credentials carry the `secret` role (never returned to the browser, only whether they are set). On the first start after an upgrade the plugin moves the retired `dsh-calendar` section of `settings.yaml` into the profile — filling only absent keys, once, with values already written in the profile winning. Hosts on 0.1.5 / 0.1.6 keep the previous namespace registration unchanged. The verified host baseline is official-source **0.
|
|
18
|
-
|
|
19
|
-
Version **0.8.4** (2026-09-23) supports the Web mount paths introduced in Harness **0.1.7**. The calendar panel keeps its requests under the application path when deployed through a reverse proxy, preserving normal settings and event operations.
|
|
20
|
-
|
|
21
|
-
2026-09-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
- `
|
|
66
|
-
- `
|
|
67
|
-
- `
|
|
68
|
-
- `
|
|
69
|
-
- `
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
##
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
- `
|
|
174
|
-
- `
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
- **
|
|
187
|
-
- **
|
|
188
|
-
- **
|
|
189
|
-
- **
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
-
|
|
208
|
-
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
pnpm install
|
|
219
|
-
pnpm test # 构建 + node --test
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
Build output in `lib/`; tests in `test/*.test.mjs` (no real account needed).
|
|
1
|
+
# dsh-calendar
|
|
2
|
+
|
|
3
|
+
## 0.9.3 update (2026-10-01)
|
|
4
|
+
|
|
5
|
+
Switching from Google to iCloud, Nextcloud or a custom server uses the new provider's authentication method for connection tests and saves. Environment credentials display a configured indicator without exposing their values. Editing the form clears the previous connection result.
|
|
6
|
+
|
|
7
|
+
Validation host: Harness `0.2.0-rc.2` built from official sources (commit `639ed01539`) on 2026-10-01. All 178 Windows tests, the 18-plugin co-load and six calendar tool contracts pass. Browser checks use an isolated profile and a local CalDAV fixture: provider switching, connection testing, saving, event editing, month/week/agenda views and a 900px-wide window. Successful production Google/iCloud account flows still require separate validation.
|
|
8
|
+
|
|
9
|
+
   
|
|
10
|
+
|
|
11
|
+
[](https://awesome-dsh-plugin.com)
|
|
12
|
+
|
|
13
|
+
DSH community plugin: read/write calendar events via CalDAV. Provides 5 calendar tools (calendar_list / calendar_create / calendar_update / calendar_delete / calendar_search) plus the offline `calendar_health` configuration check. Google uses OAuth 2.0; iCloud / Nextcloud / custom servers retain Basic authentication by default. Includes visual week/month views, drag and keyboard rescheduling, connection settings and connection tests. Profile cordis.patch.yml configuration is also supported. Updates and deletes fetch the known event directly, avoiding a collection-wide index request.
|
|
14
|
+
|
|
15
|
+
## Compatibility
|
|
16
|
+
|
|
17
|
+
Version **0.9.0** (2026-09-23) follows the settings interface introduced in Harness **0.1.7**: the host removed `ctx.settings.register`, so configuration now lives in the entry's own `Config`. Every connection field is declared `volatile` (panel edits apply live, no restart) and the three credentials carry the `secret` role (never returned to the browser, only whether they are set). On the first start after an upgrade the plugin moves the retired `dsh-calendar` section of `settings.yaml` into the profile — filling only absent keys, once, with values already written in the profile winning. Hosts on 0.1.5 / 0.1.6 keep the previous namespace registration unchanged. The verified host baseline is official-source **0.2.0-rc.2**; live CalDAV operations still require their own account validation.
|
|
18
|
+
|
|
19
|
+
Version **0.8.4** (2026-09-23) supports the Web mount paths introduced in Harness **0.1.7**. The calendar panel keeps its requests under the application path when deployed through a reverse proxy, preserving normal settings and event operations.
|
|
20
|
+
|
|
21
|
+
On 2026-09-10, npm `dsh-calendar@0.5.2` passed installation through this Harness release's official CLI and registration of all 6 tools. Host execution of `calendar_list` refreshed a real Google token (200), read via CalDAV REPORT (207) and rendered model-facing results. Only Google reads were tested; no writes were performed. This is a historical read-only verification record.
|
|
22
|
+
|
|
23
|
+
Follows the official [plugin packaging and installation requirements](https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/user/develop/basic/publish.md): an ESM entry point, prebuilt `lib/`, `dsh.bundle.patch` and a `cordis.patch.yml` layer. The plugin explicitly injects `tools` and supplies JSON Schema parameters, canonical output and rendering, with no runtime imports of `@deepseek-ai/*` internals. Use Node 22.19 or later within 22.x, or Node 24 or later. Harness is evolving rapidly; the version above is the tested baseline.
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
dsh plugin --profile web add dsh-calendar
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
After installing, restart dsh. The plugin inserts a `calendar` config line into the profile (see this package's cordis.patch.yml). The default provider is custom with no credentials filled in; the plugin still loads, but tools throw a Chinese guidance error when called, prompting you to complete the configuration.
|
|
32
|
+
|
|
33
|
+
## Uninstall
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
dsh plugin --profile web remove dsh-calendar
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Then restart the web service. To clean up fully, also remove the plugin entry from your profile `cordis.patch.yml` if you overrode it.
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
## Configuration
|
|
43
|
+
|
|
44
|
+
**Recommended: configure it in the panel (0.6.0+)**
|
|
45
|
+
|
|
46
|
+
Open「Connection」in the panel toolbar, pick a provider, fill in the address and account, hit **Test connection** (it really lists the next 30 days) and then **Save and enable**. The tools pick the new configuration up on their next call — no restart, no YAML editing.
|
|
47
|
+
|
|
48
|
+
What the panel saves lives in your local `settings.yaml` under the `dsh-calendar` namespace (the password is a secret field: no logs, no exports). If the host has no settings service (or the namespace cannot be registered), the connection is stored in the plugin’s own file `$DSH_HOME/data/dsh-calendar/connection.json` (0600) instead, and the panel says which one is in use. **The YAML remains the base layer**: any field the panel never touched still comes from it, so an existing `cordis.patch.yml` setup needs no migration; a headless host without a settings page still configures through YAML only.
|
|
49
|
+
|
|
50
|
+
Per-provider notes:
|
|
51
|
+
|
|
52
|
+
- **iCloud**: `caldavUrl` looks like `https://caldav.icloud.com/<numeric id>/calendars/<calendar id>/`; use an [app-specific password](https://appleid.apple.com/)
|
|
53
|
+
- **Nextcloud / self-hosted**: server URL + username + calendar name (self-hosted: the full collection URL, keep the trailing slash) and an app password
|
|
54
|
+
- **Google**: OAuth only — Google rejects every Basic password, including app passwords; needs `clientId` / `clientSecret` / `refreshToken` / `calendarId`
|
|
55
|
+
|
|
56
|
+
### Or: write YAML (advanced / headless)
|
|
57
|
+
|
|
58
|
+
The fields below mirror the panel form one-to-one; whatever the panel never set comes from here.
|
|
59
|
+
All configuration lives in your profile's cordis.patch.yml; override the `calendar` line by id (overriding replaces that line's config wholesale). Common fields:
|
|
60
|
+
|
|
61
|
+
- `provider`: google | icloud | nextcloud | custom
|
|
62
|
+
- `caldavUrl`: full calendar collection URL (required for custom / icloud; google / nextcloud can also override the preset manually)
|
|
63
|
+
- `authMethod`: `basic` | `oauth`; Google defaults to and requires `oauth`, other providers default to `basic`. Opt in explicitly for another OAuth server. Google credentials in the environment never switch a Basic provider to OAuth automatically.
|
|
64
|
+
- `username` / `password`: required only for Basic authentication. Use an app-specific password for iCloud, preferably via `DSH_CALENDAR_PASSWORD`. **Google CalDAV rejects all Basic passwords, including app-specific passwords.**
|
|
65
|
+
- `clientId` / `clientSecret` / `refreshToken`: required for OAuth; also read from `DSH_CALENDAR_CLIENT_ID` / `DSH_CALENDAR_CLIENT_SECRET` / `DSH_CALENDAR_REFRESH_TOKEN`. Non-empty config values take precedence over environment variables. Never commit secrets or tokens to Git.
|
|
66
|
+
- `tokenUrl`: also read from `DSH_CALENDAR_TOKEN_URL`; defaults to `https://oauth2.googleapis.com/token` for Google and is required for other OAuth providers. OAuth tokenUrl and caldavUrl must use HTTPS without embedded credentials, query parameters or fragments.
|
|
67
|
+
- `calendarId`: google-specific, the calendar ID (usually your email)
|
|
68
|
+
- `host` / `user` / `calendar`: nextcloud-specific
|
|
69
|
+
- `proxyUrl`: optional HTTP proxy (e.g. `http://127.0.0.1:7890`); both OAuth token requests and CalDAV requests use this transport
|
|
70
|
+
|
|
71
|
+
### Google example
|
|
72
|
+
|
|
73
|
+
```yaml
|
|
74
|
+
- id: calendar
|
|
75
|
+
name: dsh-calendar
|
|
76
|
+
config:
|
|
77
|
+
provider: google
|
|
78
|
+
calendarId: you@gmail.com
|
|
79
|
+
# authMethod: oauth # the default for Google
|
|
80
|
+
# Provide clientId / clientSecret / refreshToken via environment variables
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Google's CalDAV collection URL is assembled by the plugin: `https://apidata.googleusercontent.com/caldav/v2/<calendarId>/events`.
|
|
84
|
+
|
|
85
|
+
Set these placeholder values in the same terminal that starts source-run `pnpm dsh` or ordinary `dsh`:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
export DSH_CALENDAR_CLIENT_ID='your OAuth client ID'
|
|
89
|
+
export DSH_CALENDAR_CLIENT_SECRET='your OAuth client secret'
|
|
90
|
+
export DSH_CALENDAR_REFRESH_TOKEN='your authorized refresh token'
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
These credentials come from your own Google Cloud OAuth client and a user authorization, not an email app password. Follow the [Google CalDAV setup guide](https://developers.google.com/workspace/calendar/caldav/v2/guide) to enable the API and configure OAuth. Request `https://www.googleapis.com/auth/calendar` for calendar read/write and offline access (`access_type=offline`) to obtain a refresh token; see [Google's offline authorization documentation](https://developers.google.com/identity/protocols/oauth2/web-server#offline). Supply an already authorized refresh token, or mint one with the helper below.
|
|
94
|
+
**Getting a refresh token (one command)**: register the client as a **Desktop app**, then:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
cd <plugin directory> # the source checkout, or node_modules/dsh-calendar
|
|
98
|
+
node scripts/google-oauth.mjs --client-id <your clientId> --client-secret <your clientSecret>
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The script spins up a **one-shot local callback** (`http://127.0.0.1:<random port>/`), opens the browser for consent, exchanges the code and prints the refresh token — no hand-built URLs, no third-party playground.
|
|
102
|
+
|
|
103
|
+
Paste it into the panel’s「Connection」form (provider Google, calendarId = your Gmail address), or use the `DSH_CALENDAR_CLIENT_ID` / `DSH_CALENDAR_CLIENT_SECRET` / `DSH_CALENDAR_REFRESH_TOKEN` environment variables.
|
|
104
|
+
|
|
105
|
+
> While the OAuth consent screen is still in **Testing**, the refresh token lasts 7 days: hit **Publish app** on the OAuth consent screen to make it permanent (no review needed for personal use).
|
|
106
|
+
|
|
107
|
+
**Google CalDAV scope validation (2026-09-08):** for the same account and calendar, `calendar.readonly` allowed token refresh (200) and collection PROPFIND (207), but the event REPORT returned 403. With the `calendar` scope above, REPORT returned 207; a fresh verification process also refreshed the token and read successfully. Use this CalDAV scope configuration and verify an actual `calendar_list` request: successful token acquisition or collection discovery alone does not prove events are readable. This live test performed reads only; Google write operations were not tested.
|
|
108
|
+
|
|
109
|
+
The `calendar` scope permits viewing, editing, sharing and deleting accessible calendars; review that access before granting it. See [Google's scope definitions](https://developers.google.com/workspace/calendar/api/auth). For an external OAuth app in **Testing**, a refresh token with Calendar scopes expires after 7 days. Long-term use needs reauthorization or an appropriate production configuration under Google's requirements; see [refresh token expiration](https://developers.google.com/identity/protocols/oauth2#expiration).
|
|
110
|
+
|
|
111
|
+
Access tokens are cached in memory and checked before each DAV request, with refresh before expiry. Both token and DAV requests inherit the tool call's AbortSignal. A 401 invalidates the token for the next call; **writes are never replayed automatically**. OAuth requests do not follow redirects or send Bearer tokens to cross-origin object hrefs, so configure the final calendar collection URL. Runtime tokens are not written to configuration or logs. If another OAuth provider rotates refresh tokens, supply valid credentials again when restarting.
|
|
112
|
+
|
|
113
|
+
### iCloud example
|
|
114
|
+
|
|
115
|
+
```yaml
|
|
116
|
+
- id: calendar
|
|
117
|
+
name: dsh-calendar
|
|
118
|
+
config:
|
|
119
|
+
provider: icloud
|
|
120
|
+
username: you@icloud.com
|
|
121
|
+
caldavUrl: https://caldav.icloud.com/123456789/calendars/<日历ID>/
|
|
122
|
+
# password 推荐用环境变量 DSH_CALENDAR_PASSWORD
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
iCloud needs the full calendar collection URL (with your user ID and calendar ID); you can find the specific calendar address in the calendar CalDAV settings on icloud.com.
|
|
126
|
+
|
|
127
|
+
### Nextcloud example
|
|
128
|
+
|
|
129
|
+
```yaml
|
|
130
|
+
- id: calendar
|
|
131
|
+
name: dsh-calendar
|
|
132
|
+
config:
|
|
133
|
+
provider: nextcloud
|
|
134
|
+
username: alice
|
|
135
|
+
host: https://cloud.example.com
|
|
136
|
+
user: alice
|
|
137
|
+
calendar: personal
|
|
138
|
+
# password 推荐用环境变量 DSH_CALENDAR_PASSWORD
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
The plugin assembles: `https://cloud.example.com/remote.php/dav/calendars/alice/personal/`.
|
|
142
|
+
|
|
143
|
+
### Custom CalDAV example
|
|
144
|
+
|
|
145
|
+
```yaml
|
|
146
|
+
- id: calendar
|
|
147
|
+
name: dsh-calendar
|
|
148
|
+
config:
|
|
149
|
+
provider: custom
|
|
150
|
+
caldavUrl: https://dav.example.com/calendars/me/work/
|
|
151
|
+
username: me
|
|
152
|
+
# password 推荐用环境变量 DSH_CALENDAR_PASSWORD
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Authentication troubleshooting
|
|
156
|
+
|
|
157
|
+
Google: OAuth 2.0 only. On 401/403, check OAuth authorization, calendar scope and calendar permissions. If refresh and PROPFIND succeed but REPORT returns 403, check that the granted scope is the `calendar` scope above; successful discovery with `calendar.readonly` does not prove events are readable. On refresh failure, verify clientId/clientSecret/refreshToken, re-authorize revoked or expired grants, and check the 7-day Testing lifetime. **Generating another app password cannot fix Google CalDAV authentication.**
|
|
158
|
+
|
|
159
|
+
iCloud: sign in to appleid.apple.com → Sign-In and Security → App-Specific Passwords, generate one and fill it into `password` or `DSH_CALENDAR_PASSWORD`. You cannot use your Apple ID password.
|
|
160
|
+
|
|
161
|
+
Nextcloud / custom Basic servers: check the account, password or provider-issued app token and calendar permissions. `calendar_health` checks configuration only; it neither connects nor proves authorization. Use `calendar_list` to verify the real connection.
|
|
162
|
+
|
|
163
|
+
## Proxy
|
|
164
|
+
|
|
165
|
+
If your CalDAV server is not directly reachable from your network (some regional or corporate networks block it), set `proxyUrl` in the plugin config, e.g. `http://127.0.0.1:7890`, and restart. Both token refresh and DAV requests use that HTTP proxy; it does not affect other plugins in the same process.
|
|
166
|
+
|
|
167
|
+
## Tool reference
|
|
168
|
+
|
|
169
|
+
- `calendar_health`: offline provider, endpoint and Basic/OAuth credential-completeness checks; never displays secrets or connects to the server.
|
|
170
|
+
- `calendar_list`: list events in a time range (start/end, ISO 8601; defaults to the next 7 days). Recurring events are expanded by default (`expand` defaults to true, `maxOccurrences` defaults to 30, clamped to 1-200): each occurrence is a separate row with `isOccurrence: true` and `seriesStart`; non-recurring events keep `isOccurrence: false`. With `expand=false`, recurring events are returned as a single original entry with `rrule`. Results are stably sorted by start time.
|
|
171
|
+
- `calendar_create`: create an event (summary/start/end required; description/location/allDay/rrule optional). Validates real calendar dates and `end >= start`.
|
|
172
|
+
- `calendar_update`: edit an event by uid (summary/start/end/description/location/allDay/rrule optional; omitted fields keep their original values, including the recurrence rule and original properties such as ATTENDEE / ORGANIZER / VALARM).
|
|
173
|
+
- `calendar_delete`: delete an event by uid
|
|
174
|
+
- `calendar_search`: search events by keyword within the `start`~`end` window (defaults to one year before/after now; client-side filter over title/description/location/UID, case-insensitive; `limit` defaults to 50, clamped to 1-200, and results are sorted by start time).
|
|
175
|
+
|
|
176
|
+
The stable event identifier `uid` is the CalDAV href (full object URL); `calendar_update` / `calendar_delete` use it.
|
|
177
|
+
|
|
178
|
+
## Calendar panel in Settings (0.6.0+)
|
|
179
|
+
|
|
180
|
+
Once installed, DSH grows a「Calendar」section in Settings; the chat page also gets a small 📅 button in the bottom-right that opens the same panel (non-modal, Esc closes).
|
|
181
|
+
|
|
182
|
+
- **Three views**: month (6×7 grid, today highlighted, click a cell to create), week (24-hour time grid starting at 07:00, overlapping events side by side) and agenda (grouped by day). The panel **remembers the view you used last**.
|
|
183
|
+
- **Drag to reschedule**: in the week view drag an event up/down to change the time, left/right to change the day (15-minute snapping), and drag its bottom edge to change the duration. A drop preview shows where it lands and turns yellow when it overlaps something (overlaps are allowed, it is only a warning). Keyboard works too: `↑/↓` 15 minutes, `Shift+↑/↓` an hour, `←/→` a day, `Esc` cancels mid-drag. Every move offers a 5-second undo and rolls back (then re-reads the server) if the request fails. **Recurring events and events longer than 24 hours cannot be dragged yet** — edit those in the details drawer.
|
|
184
|
+
- **Click to edit**: open any event in the right-hand drawer — time, duration, a human-readable repeat rule (`FREQ=WEEKLY;COUNT=6` → "weekly (6 times)"), location, notes, one-click uid copy — then edit or delete (deletion asks twice).
|
|
185
|
+
- **Create**: title, date, start/end, all-day, location, notes, repeat (daily/weekly/monthly/yearly, or a raw RRULE).
|
|
186
|
+
- **Conflict notice**: before saving, overlapping events are listed and you are asked whether to continue.
|
|
187
|
+
- **Configurable from the panel itself**: without a CalDAV account the panel shows guidance whose「Connection」button opens the form (test first, then save); a clearly labelled sample is one click away and never pretends to be your real calendar.
|
|
188
|
+
- **Theme and locale aware**: colours come from the host design tokens (`--dsw-*`), so light/dark themes follow automatically; copy follows Settings → General.
|
|
189
|
+
- **Security**: the panel only talks to the plugin’s own `/_dsh/dsh-calendar/settings`, which is loopback-only and re-checks Host, Origin and Content-Type on writes; credentials are never echoed back.
|
|
190
|
+
## Time and timezone
|
|
191
|
+
|
|
192
|
+
Input and output are uniformly ISO 8601. Timed events are output in UTC (e.g. `2025-01-15T01:00:00Z`); all-day events output `YYYY-MM-DD`. Input may carry a timezone offset (e.g. `2025-01-15T09:00:00+08:00`); the plugin converts to UTC internally for storage.
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
## Changelog
|
|
196
|
+
|
|
197
|
+
- **0.5.4 (2026-09-18)**: fixes for `calendar_update` dropping ATTENDEE/VALARM and other original properties, ignored RECURRENCE-ID overrides, `calendar_create` inventing a uid instead of using the server Location, and silent empty expansion results; `calendar_search` gained a time range. 79 tests.
|
|
198
|
+
- **0.5.3 (2026-09-11)**: revalidated against official Harness 0.1.5-rc.1 and refreshed co-load / live-service verification; runtime code unchanged.
|
|
199
|
+
- **0.5.2 and earlier**: see [CHANGELOG.md](CHANGELOG.md).
|
|
200
|
+
|
|
201
|
+
## Known limitations
|
|
202
|
+
|
|
203
|
+
- Recurring event expansion: calendar_list expands RRULE by default via ICAL.RecurExpansion (`expand=true`), capped by `maxOccurrences`, and raises an explicit error when the iteration budget is exhausted; calendar_search only queries the `start`~`end` window (default one year before/after now) and still returns the original series (not expanded).
|
|
204
|
+
- Single-instance reads vs edit/delete: calendar_list honors RECURRENCE-ID overrides when expanding (an occurrence separately rescheduled/retitled is returned with the override time and fields, including when the original instant is EXDATE-excluded); calendar_update / calendar_delete still operate on the whole recurring series (by uid) and cannot modify or delete just one occurrence.
|
|
205
|
+
- OAuth credentials must be obtained beforehand: refresh-token authentication is supported, but there is no browser login UI / login CLI and runtime tokens are not written back to configuration.
|
|
206
|
+
- Timezone rules: events with TZID (named timezone) are output converted to UTC (Z); all-day boundaries, DST, and other complex timezone rules are not handled finely.
|
|
207
|
+
- Calendar discovery: iCloud requires manually filling the full calendar collection URL; no principal auto-discovery or multi-calendar selection.
|
|
208
|
+
- Cancellation/timeout: tools use `timeoutMs` (60 seconds) and forward the host AbortSignal into token refresh and DAV network requests. Concurrent calls cancel independently.
|
|
209
|
+
|
|
210
|
+
## Development
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
pnpm install
|
|
214
|
+
pnpm test # 构建 + node --test
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Build output in `lib/`; tests in `test/*.test.mjs` (no real account needed).
|
package/README.md
CHANGED
|
@@ -1,258 +1,251 @@
|
|
|
1
|
-
[English](README.en.md)
|
|
2
|
-
|
|
3
|
-
# dsh-calendar
|
|
4
|
-
|
|
5
|
-
## 0.9.
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
验证宿主:官方源码构建的 Harness `0.2.0-rc.
|
|
10
|
-
|
|
11
|
-
> **agent 从此会排期**:CalDAV 读写日历,重复日程自动展开。
|
|
12
|
-
|
|
13
|
-
   
|
|
14
|
-
|
|
15
|
-
[](https://awesome-dsh-plugin.com)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
DSH 社区插件:通过 CalDAV 读写日历事件。提供 5 个日历操作工具(calendar_list / calendar_create / calendar_update / calendar_delete / calendar_search)和 `calendar_health` 配置自检。Google 使用 OAuth 2.0;iCloud / Nextcloud / 自定义服务器默认保留 Basic 认证。提供可视化周/月日历、拖拽与键盘改期、连接设置和连通性测试;也支持通过 profile 的 cordis.patch.yml 配置。已知事件的更新与删除直接读取该事件,减少一次整集合索引请求。
|
|
19
|
-
|
|
20
|
-
## 兼容性
|
|
21
|
-
|
|
22
|
-
**0.9.0
|
|
23
|
-
|
|
24
|
-
**0.8.4(2026-09-23)**适配 Harness **0.1.7** 的 Web
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
- `
|
|
62
|
-
- `
|
|
63
|
-
- `
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
-
|
|
213
|
-
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
##
|
|
225
|
-
|
|
226
|
-
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
构建产物在 `lib/`,测试在 `test/*.test.mjs`(不依赖真实账号)。
|
|
253
|
-
|
|
254
|
-
## 相关插件
|
|
255
|
-
|
|
256
|
-
- [dsh-calendar](https://github.com/STARDUSTLC666/dsh-calendar) — CalDAV 日历五件套
|
|
257
|
-
- [dsh-slack](https://github.com/STARDUSTLC666/dsh-slack) — Slack 通知/收件箱
|
|
258
|
-
- [dsh-dingtalk](https://github.com/STARDUSTLC666/dsh-dingtalk) — 钉钉群通知(零依赖)
|
|
1
|
+
[English](README.en.md)
|
|
2
|
+
|
|
3
|
+
# dsh-calendar
|
|
4
|
+
|
|
5
|
+
## 0.9.3 更新(2026-10-01)
|
|
6
|
+
|
|
7
|
+
从 Google 切换到 iCloud、Nextcloud 或自建服务时,连接测试与保存使用新服务商的认证方式。环境变量中的凭据会显示“已配置,留空不改”;修改表单后清除旧的连接测试结论。
|
|
8
|
+
|
|
9
|
+
验证宿主:官方源码构建的 Harness `0.2.0-rc.2`(commit `639ed01539`,2026-10-01)。178 项 Windows 插件测试、18 插件共同加载与 6 个日历工具的契约检查通过。浏览器使用隔离 profile 和本地 CalDAV 测试服务,完成服务商切换、连接测试、保存、日程编辑及月/周/议程视图检查,检查了 900px 宽的窗口。真实 Google/iCloud 等账号的业务验收仍需单独完成。
|
|
10
|
+
|
|
11
|
+
> **agent 从此会排期**:CalDAV 读写日历,重复日程自动展开。
|
|
12
|
+
|
|
13
|
+
   
|
|
14
|
+
|
|
15
|
+
[](https://awesome-dsh-plugin.com)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
DSH 社区插件:通过 CalDAV 读写日历事件。提供 5 个日历操作工具(calendar_list / calendar_create / calendar_update / calendar_delete / calendar_search)和 `calendar_health` 配置自检。Google 使用 OAuth 2.0;iCloud / Nextcloud / 自定义服务器默认保留 Basic 认证。提供可视化周/月日历、拖拽与键盘改期、连接设置和连通性测试;也支持通过 profile 的 cordis.patch.yml 配置。已知事件的更新与删除直接读取该事件,减少一次整集合索引请求。
|
|
19
|
+
|
|
20
|
+
## 兼容性
|
|
21
|
+
|
|
22
|
+
**0.9.0** 起适配 Harness **0.1.7** 的设置接口:宿主移除了 `ctx.settings.register`,设置项改由 entry 的 `Config` 承载。连接字段全部声明为 `volatile`(面板改完即生效,无需重启),三个凭据按 `secret` 处理(不回传浏览器,只回「有没有」);升级后首次启动会把老 `settings.yaml` 里的 `dsh-calendar` 段搬进 profile(只补空缺、只搬一次,profile 里已写过的值优先)。0.1.5 / 0.1.6 宿主仍走原来的命名空间注册,行为不变。当前验证宿主为官方源码构建的 **0.2.0-rc.2**;真实 CalDAV 业务仍需有效账号单独验证。
|
|
23
|
+
|
|
24
|
+
**0.8.4(2026-09-23)**适配 Harness **0.1.7** 的 Web 子路径部署:日历面板的请求会跟随应用路径,修复经过反向代理访问时设置与事件操作失败的问题。真实 CalDAV 业务仍需有效账号单独验证。
|
|
25
|
+
|
|
26
|
+
遵循官方[插件打包与安装要求](https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/user/develop/basic/publish.md):ESM 入口、预构建 `lib/`、`dsh.bundle.patch` 和 `cordis.patch.yml` 配置层;显式注入 `tools`,提供 JSON Schema 参数、规范化输出和渲染函数,运行时不 import `@deepseek-ai/*` 内部模块。使用 Node 22.19 及以上的 22.x 或 Node 24 及以上版本;Harness 仍在快速迭代,上述版本是实测基线。
|
|
27
|
+
|
|
28
|
+
## 安装
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
dsh plugin --profile web add dsh-calendar
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
安装后重启 dsh。插件会向 profile 插入一行 id 为 `calendar` 的配置行(见本包的 cordis.patch.yml)。默认 provider 为 custom 且未填任何凭证,此时插件照常加载,但工具在调用时会抛出中文指引错误,提示你补全配置。
|
|
35
|
+
|
|
36
|
+
## 配置
|
|
37
|
+
|
|
38
|
+
**推荐:在面板里配(0.6.0+)**
|
|
39
|
+
|
|
40
|
+
打开面板右上角的「连接设置」,选服务商 → 填地址与账号 → 点**「测试连接」**(它会真的去列未来 30 天的日程)→ 通过后点「保存并启用」。工具的下一次调用立刻用上新配置,不用重启、不用编辑 YAML。
|
|
41
|
+
|
|
42
|
+
面板写下的值优先存在本机 `settings.yaml` 的 `dsh-calendar` 命名空间里(密码是 secret 字段:不进日志、不进导出);若宿主没有 settings 服务(或命名空间注册失败),自动改存插件自己的文件 `$DSH_HOME/data/dsh-calendar/connection.json`(0600),面板里会明确显示当前存在哪。**YAML 仍然是基座**:面板没填过的字段由它兜底,所以已经用 `cordis.patch.yml` 配好的部署零迁移;纯 headless(没有设置页)的宿主依然只能走 YAML。
|
|
43
|
+
|
|
44
|
+
各服务商要点:
|
|
45
|
+
|
|
46
|
+
- **iCloud**:`caldavUrl` 形如 `https://caldav.icloud.com/<数字ID>/calendars/<日历ID>/`,密码用 [App 专用密码](https://appleid.apple.com/)
|
|
47
|
+
- **Nextcloud / 自建**:服务器地址 + 用户名 + 日历名(自建则给完整集合 URL,结尾的 `/` 别丢),密码用 App 密码
|
|
48
|
+
- **Google**:必须 OAuth,Google 不接受任何 Basic 密码;需要 `clientId` / `clientSecret` / `refreshToken` / `calendarId`
|
|
49
|
+
|
|
50
|
+
### 或者:写 YAML(高级 / headless)
|
|
51
|
+
|
|
52
|
+
下面的字段与面板表单一一对应;面板没设过的字段以这里为准。
|
|
53
|
+
所有配置都在你的 profile 的 cordis.patch.yml 里,按 id 覆盖 `calendar` 行(覆盖整行的 config)。通用字段:
|
|
54
|
+
|
|
55
|
+
- `provider`:google | icloud | nextcloud | custom
|
|
56
|
+
- `caldavUrl`:完整日历集合 URL(custom / icloud 必填;google / nextcloud 也可手填覆盖预设)
|
|
57
|
+
- `authMethod`:`basic` | `oauth`;Google 默认且必须为 `oauth`,其他 provider 默认 `basic`。其他 OAuth 服务器需显式设置 `oauth`,不会因环境中存在 Google 凭据而自动切换。
|
|
58
|
+
- `username` / `password`:仅 Basic 认证必需。iCloud 请用应用专用密码;密码推荐用环境变量 `DSH_CALENDAR_PASSWORD`。**Google CalDAV 不接受任何 Basic 密码,包括应用专用密码。**
|
|
59
|
+
- `clientId` / `clientSecret` / `refreshToken`:OAuth 必需;分别支持 `DSH_CALENDAR_CLIENT_ID` / `DSH_CALENDAR_CLIENT_SECRET` / `DSH_CALENDAR_REFRESH_TOKEN`。非空配置值优先于环境变量。请勿把密钥或令牌提交到 Git。
|
|
60
|
+
- `tokenUrl`:可用 `DSH_CALENDAR_TOKEN_URL`;Google 默认 `https://oauth2.googleapis.com/token`,其他 OAuth 服务必填。OAuth 的 tokenUrl 与 caldavUrl 必须为不含内嵌账号密码、查询参数或片段的 HTTPS 地址。
|
|
61
|
+
- `proxyUrl`:可选 HTTP 代理地址(如 http://127.0.0.1:7890);令牌刷新和 CalDAV 请求共用该代理。可直连时无需填写。
|
|
62
|
+
- `calendarId`:google 专用,日历 ID(通常是你的邮箱)
|
|
63
|
+
- `host` / `user` / `calendar`:nextcloud 专用
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
## 卸载
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
dsh plugin --profile web remove dsh-calendar
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
卸载后重启 Web 服务。如需彻底清理,可再手动删除自己 profile `cordis.patch.yml` 中的对应插件行。
|
|
73
|
+
|
|
74
|
+
## 中国用户:特殊代理配置(Google / iCloud)
|
|
75
|
+
|
|
76
|
+
若本机网络无法直连 Google / iCloud,插件内置的 `proxyUrl` 可把 OAuth 令牌请求和 CalDAV 请求路由到**你本机 HTTP 代理客户端的端口**,不影响其他插件,也无需改任何系统设置。
|
|
77
|
+
|
|
78
|
+
```yaml
|
|
79
|
+
- id: calendar
|
|
80
|
+
config:
|
|
81
|
+
provider: google
|
|
82
|
+
calendarId: you@gmail.com
|
|
83
|
+
# OAuth 凭据通过下文三个环境变量提供
|
|
84
|
+
proxyUrl: http://127.0.0.1:7890 # 改成你代理客户端的本地端口
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 常见代理客户端本地端口
|
|
88
|
+
|
|
89
|
+
| 客户端 | 本地端口 |
|
|
90
|
+
|---|---|
|
|
91
|
+
| Clash / Clash Verge(HTTP) | 7890 / 7897 |
|
|
92
|
+
| v2rayN(HTTP / SOCKS) | 10808 / 10809 |
|
|
93
|
+
| Shadowsocks | 1080 |
|
|
94
|
+
|
|
95
|
+
在客户端界面确认你的实际端口,填进 `proxyUrl` 即可。国内可直连的 CalDAV 服务(如自建 Nextcloud)则无需填写。
|
|
96
|
+
|
|
97
|
+
### Google 示例
|
|
98
|
+
|
|
99
|
+
```yaml
|
|
100
|
+
- id: calendar
|
|
101
|
+
name: dsh-calendar
|
|
102
|
+
config:
|
|
103
|
+
provider: google
|
|
104
|
+
calendarId: you@gmail.com
|
|
105
|
+
# authMethod: oauth # Google 默认即为 oauth
|
|
106
|
+
# clientId / clientSecret / refreshToken 推荐用环境变量
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Google 的 CalDAV 集合 URL 由插件拼成:`https://apidata.googleusercontent.com/caldav/v2/<calendarId>/events`。
|
|
110
|
+
|
|
111
|
+
在启动源码版 `pnpm dsh` 或普通 `dsh` 的同一个终端中设置(以下均为占位值):
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
export DSH_CALENDAR_CLIENT_ID='你的 OAuth 客户端 ID'
|
|
115
|
+
export DSH_CALENDAR_CLIENT_SECRET='你的 OAuth 客户端密钥'
|
|
116
|
+
export DSH_CALENDAR_REFRESH_TOKEN='你授权后取得的 refresh token'
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
凭据必须来自你自己的 Google Cloud OAuth 客户端和一次用户授权,而不是邮箱应用专用密码。按 [Google CalDAV 官方设置说明](https://developers.google.com/workspace/calendar/caldav/v2/guide) 启用 API、配置 OAuth;申请日历读写范围 `https://www.googleapis.com/auth/calendar`,并请求离线访问(`access_type=offline`)以取得刷新令牌,参见 [Google 离线授权说明](https://developers.google.com/identity/protocols/oauth2/web-server#offline)。已有 OAuth 配置的用户可直接填入刷新令牌。
|
|
120
|
+
|
|
121
|
+
**换 refresh token(一条命令)**:客户端类型选「桌面应用」,然后:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
cd <插件目录> # 源码仓库,或 node_modules/dsh-calendar
|
|
125
|
+
node scripts/google-oauth.mjs --client-id 你的clientId --client-secret 你的clientSecret
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
脚本会起一个**一次性本地回调**(`http://127.0.0.1:<随机端口>/`)、打开浏览器让你授权、收到 code 后换成 refresh token 并打印出来 —— 不用手工拼 URL,也不用第三方 Playground。
|
|
129
|
+
|
|
130
|
+
拿到后填进面板「连接设置」(provider 选 Google,calendarId 填你的 Gmail 地址);想走 YAML 的话就用环境变量 `DSH_CALENDAR_CLIENT_ID` / `DSH_CALENDAR_CLIENT_SECRET` / `DSH_CALENDAR_REFRESH_TOKEN`。
|
|
131
|
+
|
|
132
|
+
> 同意屏仍停在「测试」状态时 refresh token 只有 7 天有效:到「OAuth 同意屏幕」点一下「发布应用」即可长期有效(个人自用无需审核)。
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
**Google CalDAV 范围实测(2026-09-08)**:同一账号与日历使用 `calendar.readonly` 时,令牌刷新返回 200、集合探测 PROPFIND 返回 207,但读取日程 REPORT 返回 403;改为上述 `calendar` 范围后,REPORT 返回 207,重启验证进程后再次刷新和读取也通过。因此,请按这里的 CalDAV 配置申请范围,并用 `calendar_list` 验证真实读取,不能只凭令牌获取成功或集合探测成功判断日程可读。本次真实测试仅执行读取,没有验证 Google 的写入操作。
|
|
136
|
+
|
|
137
|
+
`calendar` 范围允许查看、修改、分享及删除可访问的日历,申请前请确认接受该权限范围,详见 [Google 权限定义](https://developers.google.com/workspace/calendar/api/auth)。外部 OAuth 应用处于 **Testing** 状态时,包含日历权限的刷新令牌会在 7 天后过期;长期使用需处理重新授权或按 Google 要求配置生产状态,详见 [刷新令牌到期规则](https://developers.google.com/identity/protocols/oauth2#expiration)。
|
|
138
|
+
|
|
139
|
+
插件在内存中缓存访问令牌,并在每次 DAV 请求前检查有效期、提前刷新;令牌请求与 DAV 请求都会透传调用的取消信号。401 会使缓存失效,下一次调用重新刷新,**不会自动重放写请求**。OAuth 请求不跟随重定向、不向其它源的对象 href 发送 Bearer token,请填写最终日历集合地址。运行时令牌不会写入配置或日志;若其他 OAuth 提供方轮换 refresh token,重启时需要重新提供有效凭据。
|
|
140
|
+
|
|
141
|
+
### iCloud 示例
|
|
142
|
+
|
|
143
|
+
```yaml
|
|
144
|
+
- id: calendar
|
|
145
|
+
name: dsh-calendar
|
|
146
|
+
config:
|
|
147
|
+
provider: icloud
|
|
148
|
+
username: you@icloud.com
|
|
149
|
+
caldavUrl: https://caldav.icloud.com/123456789/calendars/<日历ID>/
|
|
150
|
+
# password 推荐用环境变量 DSH_CALENDAR_PASSWORD
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
iCloud 需要完整日历集合 URL(含你的用户 ID 与日历 ID),在 icloud.com 的日历 CalDAV 设置里可找到具体日历地址。
|
|
154
|
+
|
|
155
|
+
### Nextcloud 示例
|
|
156
|
+
|
|
157
|
+
```yaml
|
|
158
|
+
- id: calendar
|
|
159
|
+
name: dsh-calendar
|
|
160
|
+
config:
|
|
161
|
+
provider: nextcloud
|
|
162
|
+
username: alice
|
|
163
|
+
host: https://cloud.example.com
|
|
164
|
+
user: alice
|
|
165
|
+
calendar: personal
|
|
166
|
+
# password 推荐用环境变量 DSH_CALENDAR_PASSWORD
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
插件会拼成:`https://cloud.example.com/remote.php/dav/calendars/alice/personal/`。
|
|
170
|
+
|
|
171
|
+
### 自定义 CalDAV 示例
|
|
172
|
+
|
|
173
|
+
```yaml
|
|
174
|
+
- id: calendar
|
|
175
|
+
name: dsh-calendar
|
|
176
|
+
config:
|
|
177
|
+
provider: custom
|
|
178
|
+
caldavUrl: https://dav.example.com/calendars/me/work/
|
|
179
|
+
username: me
|
|
180
|
+
# password 推荐用环境变量 DSH_CALENDAR_PASSWORD
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## 认证失败排查
|
|
184
|
+
|
|
185
|
+
Google:仅支持 OAuth 2.0。401/403 时检查 OAuth 授权、日历范围与日历访问权限;如果令牌刷新和 PROPFIND 成功而 REPORT 返回 403,核对实际授予的范围是否为上述 `calendar`,不要将 `calendar.readonly` 的集合探测成功当作日程读取成功。刷新失败提示会按 Google 返回的 OAuth 错误代码区分下一步:`invalid_grant` 表示授权可能已失效或与当前应用不匹配,可从连接设置重新授权;`invalid_client` / `deleted_client` 指向 OAuth 应用配置;`invalid_scope` 指向授权权限范围;HTTP 429/5xx 指向服务暂时不可用,应稍后重试。其他错误保留 HTTP 状态码和安全的错误代码供排查,不会仅因 HTTP 400 就判断授权过期,也不会显示服务端自由文本。外部 OAuth 应用处于 Testing 状态时,包含日历权限的刷新令牌会在 7 天后过期;长期使用需处理重新授权或按 Google 要求配置生产状态,详见 [刷新令牌到期规则](https://developers.google.com/identity/protocols/oauth2#expiration)。**重新生成应用专用密码不能解决 Google CalDAV 认证失败。**
|
|
186
|
+
|
|
187
|
+
iCloud:登录 appleid.apple.com → 登录与安全 → App 专用密码,生成后填到 `password` 或 `DSH_CALENDAR_PASSWORD`。不能用你的 Apple ID 密码。
|
|
188
|
+
|
|
189
|
+
Nextcloud / 自定义 Basic 服务:检查账号、密码或服务要求的应用令牌及日历权限。`calendar_health` 只检查配置完整性,不联网、不证明授权成功;请再用 `calendar_list` 验证真实连接。
|
|
190
|
+
|
|
191
|
+
## 工具清单
|
|
192
|
+
|
|
193
|
+
- `calendar_health`:离线检查服务商、日历集合地址与 Basic/OAuth 凭据完整性,不回显密钥、不发起网络连接。
|
|
194
|
+
- `calendar_list`:列出某时间段事件(start/end,ISO 8601,缺省未来 7 天)。默认展开重复事件(`expand` 默认 true,`maxOccurrences` 默认 30、clamp 1-200):每个实例独立成行,带 `isOccurrence: true` 与 `seriesStart`;非重复事件保持 `isOccurrence: false`。`expand=false` 时重复事件按原始单条返回并带 `rrule`。结果按开始时间稳定排序
|
|
195
|
+
- `calendar_create`:新建事件(summary/start/end 必填,description/location/allDay/rrule 可选)。严格校验真实日历日期与 `end >= start`
|
|
196
|
+
- `calendar_update`:按 uid 改事件(summary/start/end/description/location/allDay/rrule 可选,未提供保留原值;ATTENDEE、ORGANIZER、VALARM 等原始属性一并保留,重复规则不再丢失)
|
|
197
|
+
- `calendar_delete`:按 uid 删事件
|
|
198
|
+
- `calendar_search`:按关键词搜事件(只查询 start~end 窗口内的事件,缺省为当前时间前后各 1 年;客户端过滤标题/描述/地点/UID,不区分大小写;`limit` 默认 50、clamp 1-200,结果按开始时间排序)
|
|
199
|
+
|
|
200
|
+
事件稳定标识 `uid` 为 CalDAV href(完整对象 URL),`calendar_update` / `calendar_delete` 使用它。
|
|
201
|
+
|
|
202
|
+
## 设置页里的日历面板(0.6.0+)
|
|
203
|
+
|
|
204
|
+
装好后,DSH 的「设置 → 日历」里会多出一节面板;对话页右下角还有一个 📅 小按钮,点开是同一个面板(非模态,Esc 关闭)。
|
|
205
|
+
|
|
206
|
+
- **三种视图**:月(6×7 网格,今天高亮,点格子直接新建)、周(24 小时时间网格,重叠日程并排显示,默认落在 07:00)、议程(按天分组,最像「接下来要干什么」)。面板会**记住你上次用的视图**。
|
|
207
|
+
- **直接拖就能改期**:周视图里拖动日程上下改时间、左右换天(15 分钟吸附),拖块底部改时长;拖动中会显示落点,与已有日程重叠时描成黄色(允许重叠,只是提醒)。键盘也能改:`↑/↓` 挪 15 分钟、`Shift+↑/↓` 挪 1 小时、`←/→` 挪一天,拖动中 `Esc` 取消。改完有 5 秒可撤销,落盘失败会自动回滚并重新对账。**重复日程与超过 24 小时的日程暂不支持拖动改期**(请在详情里编辑)。
|
|
208
|
+
- **点开就能改**:点任意日程打开右侧详情 —— 时间、时长、重复规则的人话(`FREQ=WEEKLY;COUNT=6` → 「每周(共 6 次)」)、地点、备注、uid 一键复制;接着「编辑」或「删除」(删除要二次确认)。
|
|
209
|
+
- **新建**:标题、日期、开始/结束、全天、地点、备注、重复(每天/每周/每月/每年,或直接写 RRULE)。
|
|
210
|
+
- **冲突提醒**:保存前若与已有日程重叠,会把重叠的几条列出来问你是否继续。
|
|
211
|
+
- **没配置也能配**:未填 CalDAV 账号时显示引导,点「连接设置」就在面板里把账号填好(先测后存);也可以点「查看示例」先看面板长什么样,示例数据有明确标注,不会冒充你的真实日历。
|
|
212
|
+
- **跟随主题与语言**:配色全部走宿主设计令牌(`--dsw-*`),深浅色主题自动适配;文案跟随 Settings → General 的语言。
|
|
213
|
+
- **安全**:面板只与插件自己的 `/_dsh/dsh-calendar/settings` 通信;该路由仅回环地址可达,写操作还要过 Host / Origin / Content-Type 三道校验,响应里永不回显账号密码。
|
|
214
|
+
## 时间与时区
|
|
215
|
+
|
|
216
|
+
输入输出统一 ISO 8601。定时事件输出为 UTC(如 `2025-01-15T01:00:00Z`),全天事件输出 `YYYY-MM-DD`。输入可带时区偏移(如 `2025-01-15T09:00:00+08:00`),插件内部转 UTC 存储。
|
|
217
|
+
|
|
218
|
+
## 版本记录
|
|
219
|
+
|
|
220
|
+
- **0.5.4(2026-09-18)**:修复 `calendar_update` 丢 ATTENDEE/VALARM 等原始属性、RECURRENCE-ID 覆盖实例被忽略、`calendar_create` 的 uid 不用服务器 Location、展开超限静默返回空;`calendar_search` 支持时间窗。测试 79 项。
|
|
221
|
+
- **0.5.3(2026-09-11)**:复验官方 Harness 0.1.5-rc.1,更新同载与真实服务验证记录;运行时代码未变。
|
|
222
|
+
- **0.5.2 及更早**:见 [CHANGELOG.md](CHANGELOG.md)。
|
|
223
|
+
|
|
224
|
+
## 已知限制
|
|
225
|
+
|
|
226
|
+
- **网络可达性**:若无法直连,可用 `proxyUrl` 指定本机 HTTP 代理,或改用可直连的 CalDAV 端点。
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
- 重复事件展开:calendar_list 默认用 ICAL.RecurExpansion 展开 RRULE(`expand=true`),受 `maxOccurrences` 封顶,展开超出迭代预算时显式报错;calendar_search 只查 start~end 窗口(默认当前前后各 1 年),仍返回原始系列(不展开)。
|
|
230
|
+
- 单次实例的读取与改/删:calendar_list 展开时识别 RECURRENCE-ID 覆盖实例(单独改期/改标题的实例按覆盖后的时间与字段返回,被 EXDATE 排除原时间的覆盖实例仍会返回);calendar_update / calendar_delete 仍针对整个重复系列(按 uid 操作),无法只修改或删除某一次发生。
|
|
231
|
+
- OAuth 凭据需要事先取得:支持刷新令牌认证,但不提供浏览器登录 UI / 登录 CLI,也不把运行时令牌写回配置文件。
|
|
232
|
+
- 时区规则:带 TZID(命名时区)的事件输出会转成 UTC(Z);全天边界、夏令时等复杂时区规则不做精细化处理。
|
|
233
|
+
- 日历发现:iCloud 需手动填完整日历集合 URL;不做 principal 自动发现与多日历选择。
|
|
234
|
+
- 取消/超时:工具使用 timeoutMs(60 秒),并向令牌刷新与 DAV 网络请求透传宿主 AbortSignal;并发调用独立取消。
|
|
235
|
+
|
|
236
|
+
## 开发
|
|
237
|
+
|
|
238
|
+
- **UI 行为测试**:`test/render.test.mjs` 用 jsdom + 真 React 渲染周视图并派发真实事件(点击 / 拖动 / Esc / 卸载 / 边缘滚动)。改前端交互时它会替你点一遍;`pnpm test` 会一起跑。
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
pnpm install
|
|
242
|
+
pnpm test # 构建 + node --test
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
构建产物在 `lib/`,测试在 `test/*.test.mjs`(不依赖真实账号)。
|
|
246
|
+
|
|
247
|
+
## 相关插件
|
|
248
|
+
|
|
249
|
+
- [dsh-calendar](https://github.com/STARDUSTLC666/dsh-calendar) — CalDAV 日历五件套
|
|
250
|
+
- [dsh-slack](https://github.com/STARDUSTLC666/dsh-slack) — Slack 通知/收件箱
|
|
251
|
+
- [dsh-dingtalk](https://github.com/STARDUSTLC666/dsh-dingtalk) — 钉钉群通知(零依赖)
|
package/lib/client.js
CHANGED
|
@@ -194,7 +194,7 @@ const UI = {
|
|
|
194
194
|
"conn.refreshToken": "refreshToken",
|
|
195
195
|
"conn.tokenUrl": "令牌地址(可留空)",
|
|
196
196
|
"conn.proxyUrl": "代理地址(可留空)",
|
|
197
|
-
"conn.keep": "
|
|
197
|
+
"conn.keep": "已配置,留空不改",
|
|
198
198
|
"conn.test": "测试连接",
|
|
199
199
|
"conn.testing": "正在连接…",
|
|
200
200
|
"conn.save": "保存并启用",
|
|
@@ -289,7 +289,7 @@ const UI = {
|
|
|
289
289
|
"conn.refreshToken": "refreshToken",
|
|
290
290
|
"conn.tokenUrl": "Token URL (optional)",
|
|
291
291
|
"conn.proxyUrl": "Proxy URL (optional)",
|
|
292
|
-
"conn.keep": "
|
|
292
|
+
"conn.keep": "configured — leave blank to keep",
|
|
293
293
|
"conn.test": "Test connection",
|
|
294
294
|
"conn.testing": "Connecting…",
|
|
295
295
|
"conn.save": "Save and enable",
|
|
@@ -1239,7 +1239,7 @@ function EventForm(props) {
|
|
|
1239
1239
|
const { draft, events, onCancel, onSubmit, busy, error } = props;
|
|
1240
1240
|
const [form, setForm] = useState(draft);
|
|
1241
1241
|
useEffect(() => { setForm(draft); }, [draft.uid, draft.date, draft.startTime, draft.summary]);
|
|
1242
|
-
const patch = (fields) => setForm((prev) => Object.assign({}, prev, fields));
|
|
1242
|
+
const patch = (fields) => setForm((prev) => Object.assign({}, prev, fields));
|
|
1243
1243
|
const isEdit = draft.uid !== undefined;
|
|
1244
1244
|
const pseudo = asPseudoEvent(form);
|
|
1245
1245
|
const conflicts = form.summary.trim() === "" ? [] : events.filter((event) => overlaps(event, pseudo) && event.uid !== form.uid);
|
|
@@ -1408,9 +1408,12 @@ function ConnectionForm(props) {
|
|
|
1408
1408
|
const { connection, onClose, onSaved } = props;
|
|
1409
1409
|
const [form, setForm] = useState(() => connectionDraftOf(connection));
|
|
1410
1410
|
const [busy, setBusy] = useState("");
|
|
1411
|
-
const [result, setResult] = useState(null);
|
|
1412
|
-
const maySave = connection === null || connection.settingsAvailable !== false;
|
|
1413
|
-
const patch = (fields) =>
|
|
1411
|
+
const [result, setResult] = useState(null);
|
|
1412
|
+
const maySave = connection === null || connection.settingsAvailable !== false;
|
|
1413
|
+
const patch = (fields) => {
|
|
1414
|
+
setForm((prev) => Object.assign({}, prev, fields));
|
|
1415
|
+
setResult(null);
|
|
1416
|
+
};
|
|
1414
1417
|
const visible = PROVIDER_FIELDS[form.provider] || [];
|
|
1415
1418
|
const payload = () => {
|
|
1416
1419
|
const out = {};
|
|
@@ -1421,8 +1424,9 @@ function ConnectionForm(props) {
|
|
|
1421
1424
|
if (SECRET_FIELDS.indexOf(key) !== -1 && value.trim() === "") continue;
|
|
1422
1425
|
out[key] = value.trim();
|
|
1423
1426
|
}
|
|
1424
|
-
if (form.provider !== "") out.provider = form.provider;
|
|
1425
|
-
if (form.provider === "google") out.authMethod = "oauth";
|
|
1427
|
+
if (form.provider !== "") out.provider = form.provider;
|
|
1428
|
+
if (form.provider === "google") out.authMethod = "oauth";
|
|
1429
|
+
else if (form.provider !== "" && connection && form.provider !== connection.provider) out.authMethod = "basic";
|
|
1426
1430
|
return out;
|
|
1427
1431
|
};
|
|
1428
1432
|
const test = async () => {
|
package/lib/oauth.js
CHANGED
|
@@ -8,6 +8,47 @@ export class OAuthError extends Error {
|
|
|
8
8
|
this.name = 'OAuthError';
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
|
+
/** OAuth error fields are diagnostics, not display text: accept identifiers only, never descriptions. */
|
|
12
|
+
function oauthErrorDetails(response) {
|
|
13
|
+
return response.clone().json().then((body) => {
|
|
14
|
+
if (body === null || typeof body !== 'object' || Array.isArray(body))
|
|
15
|
+
return {};
|
|
16
|
+
const record = body;
|
|
17
|
+
const identifier = (value) => typeof value === 'string' && value.length <= 80 && /^[A-Za-z0-9_.-]+$/.test(value) ? value : undefined;
|
|
18
|
+
return { error: identifier(record.error), error_subtype: identifier(record.error_subtype) };
|
|
19
|
+
}).catch(() => ({}));
|
|
20
|
+
}
|
|
21
|
+
function refreshFailureMessage(status, failure) {
|
|
22
|
+
const code = failure.error?.toLowerCase();
|
|
23
|
+
const subtype = failure.error_subtype?.toLowerCase();
|
|
24
|
+
let guidance;
|
|
25
|
+
if (status === 429 || status >= 500 || code === 'temporarily_unavailable' || code === 'server_error') {
|
|
26
|
+
guidance = '日历授权服务暂时不可用。请稍后重试;若使用代理,也请确认代理连接正常。';
|
|
27
|
+
}
|
|
28
|
+
else if (code === 'invalid_grant' && subtype === 'invalid_rapt') {
|
|
29
|
+
guidance = 'Google 账号要求重新验证。请打开连接设置,重新完成日历授权后保存。';
|
|
30
|
+
}
|
|
31
|
+
else if (code === 'invalid_grant') {
|
|
32
|
+
guidance = '日历授权可能已失效或与当前应用不匹配。请打开连接设置,重新完成日历授权后保存。';
|
|
33
|
+
}
|
|
34
|
+
else if (code === 'invalid_client' || code === 'deleted_client' || code === 'unauthorized_client') {
|
|
35
|
+
guidance = '日历连接设置不正确,当前 OAuth 应用无法完成授权。请打开连接设置,核对 OAuth 应用配置后重试。';
|
|
36
|
+
}
|
|
37
|
+
else if (code === 'invalid_scope') {
|
|
38
|
+
guidance = '日历授权未包含插件所需的权限范围。请在连接设置中使用日历读写权限重新授权。';
|
|
39
|
+
}
|
|
40
|
+
else if (code === 'invalid_request' || code === 'unsupported_grant_type') {
|
|
41
|
+
guidance = '日历连接设置不正确,授权服务未接受当前请求。请打开连接设置,核对授权服务地址和 OAuth 应用配置。';
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
guidance = '日历授权服务拒绝了当前请求。请打开连接设置核对配置后重试。';
|
|
45
|
+
}
|
|
46
|
+
// 顺序固定为 OAuth 标识在前、HTTP 状态在后:测试与用户排障都按这个顺序读。
|
|
47
|
+
const diagnostic = [failure.error === undefined ? undefined : 'OAuth error: ' + failure.error,
|
|
48
|
+
failure.error_subtype === undefined ? undefined : 'subtype: ' + failure.error_subtype,
|
|
49
|
+
'HTTP ' + status].filter(Boolean).join(',');
|
|
50
|
+
return guidance + ' 诊断信息:' + diagnostic + '。';
|
|
51
|
+
}
|
|
11
52
|
/** tsdav 管理 token/expiry;此层补上失败校验、取消、代理与不泄露凭据的错误。 */
|
|
12
53
|
export function createOAuthFetch(config, transport, calendarUrl) {
|
|
13
54
|
const calendarOrigin = new URL(calendarUrl).origin;
|
|
@@ -15,9 +56,8 @@ export function createOAuthFetch(config, transport, calendarUrl) {
|
|
|
15
56
|
const tokenFetch = async (input, init) => {
|
|
16
57
|
const response = await transport(input, { ...init, redirect: 'error' });
|
|
17
58
|
if (!response.ok) {
|
|
18
|
-
//
|
|
19
|
-
throw new OAuthError(
|
|
20
|
-
'):请检查 clientId、clientSecret、refreshToken;授权撤销或过期时请重新授权。', response.status);
|
|
59
|
+
// 只读取标准 OAuth 错误标识;绝不回显自由文本描述,避免泄露凭据或服务端数据。
|
|
60
|
+
throw new OAuthError(refreshFailureMessage(response.status, await oauthErrorDetails(response)), response.status);
|
|
21
61
|
}
|
|
22
62
|
let body;
|
|
23
63
|
try {
|
package/lib/web.js
CHANGED
|
@@ -248,6 +248,8 @@ export class CalendarSettingsBackend {
|
|
|
248
248
|
const resolved = effective[key];
|
|
249
249
|
return typeof resolved === 'string' ? resolved : '';
|
|
250
250
|
};
|
|
251
|
+
const env = this.options.env ?? process.env;
|
|
252
|
+
const hasCredential = (key, envKey) => text(key).trim() !== '' || (typeof env[envKey] === 'string' && env[envKey].trim() !== '');
|
|
251
253
|
return {
|
|
252
254
|
configured,
|
|
253
255
|
...(reason !== undefined ? { reason } : {}),
|
|
@@ -266,9 +268,9 @@ export class CalendarSettingsBackend {
|
|
|
266
268
|
clientId: text('clientId'),
|
|
267
269
|
tokenUrl: text('tokenUrl'),
|
|
268
270
|
proxyUrl: text('proxyUrl'),
|
|
269
|
-
hasPassword:
|
|
270
|
-
hasClientSecret:
|
|
271
|
-
hasRefreshToken:
|
|
271
|
+
hasPassword: hasCredential('password', 'DSH_CALENDAR_PASSWORD'),
|
|
272
|
+
hasClientSecret: hasCredential('clientSecret', 'DSH_CALENDAR_CLIENT_SECRET'),
|
|
273
|
+
hasRefreshToken: hasCredential('refreshToken', 'DSH_CALENDAR_REFRESH_TOKEN'),
|
|
272
274
|
};
|
|
273
275
|
}
|
|
274
276
|
/** 兼容旧动作名:status 就是连接摘要。 */
|