@planet-matrix/mobius-model 0.6.0 → 0.9.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 +37 -0
- package/dist/index.js +706 -36
- package/dist/index.js.map +855 -59
- package/package.json +28 -16
- package/src/ai/README.md +1 -0
- package/src/ai/ai.ts +107 -0
- package/src/ai/chat-completion-ai/aihubmix-chat-completion.ts +78 -0
- package/src/ai/chat-completion-ai/chat-completion-ai.ts +270 -0
- package/src/ai/chat-completion-ai/chat-completion.ts +189 -0
- package/src/ai/chat-completion-ai/index.ts +7 -0
- package/src/ai/chat-completion-ai/lingyiwanwu-chat-completion.ts +78 -0
- package/src/ai/chat-completion-ai/ohmygpt-chat-completion.ts +78 -0
- package/src/ai/chat-completion-ai/openai-next-chat-completion.ts +78 -0
- package/src/ai/embedding-ai/embedding-ai.ts +63 -0
- package/src/ai/embedding-ai/embedding.ts +50 -0
- package/src/ai/embedding-ai/index.ts +4 -0
- package/src/ai/embedding-ai/openai-next-embedding.ts +23 -0
- package/src/ai/index.ts +4 -0
- package/src/aio/README.md +100 -0
- package/src/aio/content.ts +141 -0
- package/src/aio/index.ts +3 -0
- package/src/aio/json.ts +127 -0
- package/src/aio/prompt.ts +246 -0
- package/src/basic/README.md +20 -15
- package/src/basic/error.ts +19 -5
- package/src/basic/function.ts +2 -2
- package/src/basic/index.ts +1 -0
- package/src/basic/schedule.ts +111 -0
- package/src/basic/stream.ts +135 -25
- package/src/credential/README.md +107 -0
- package/src/credential/api-key.ts +158 -0
- package/src/credential/bearer.ts +73 -0
- package/src/credential/index.ts +4 -0
- package/src/credential/json-web-token.ts +96 -0
- package/src/credential/password.ts +170 -0
- package/src/cron/README.md +86 -0
- package/src/cron/cron.ts +87 -0
- package/src/cron/index.ts +1 -0
- package/src/drizzle/README.md +1 -0
- package/src/drizzle/drizzle.ts +1 -0
- package/src/drizzle/helper.ts +47 -0
- package/src/drizzle/index.ts +5 -0
- package/src/drizzle/infer.ts +52 -0
- package/src/drizzle/kysely.ts +8 -0
- package/src/drizzle/pagination.ts +200 -0
- package/src/email/README.md +1 -0
- package/src/email/index.ts +1 -0
- package/src/email/resend.ts +25 -0
- package/src/event/class-event-proxy.ts +6 -5
- package/src/event/common.ts +13 -3
- package/src/event/event-manager.ts +3 -3
- package/src/event/instance-event-proxy.ts +6 -5
- package/src/event/internal.ts +4 -4
- package/src/form/README.md +25 -0
- package/src/form/index.ts +1 -0
- package/src/form/inputor-controller/base.ts +874 -0
- package/src/form/inputor-controller/boolean.ts +39 -0
- package/src/form/inputor-controller/file.ts +39 -0
- package/src/form/inputor-controller/form.ts +181 -0
- package/src/form/inputor-controller/helper.ts +117 -0
- package/src/form/inputor-controller/index.ts +17 -0
- package/src/form/inputor-controller/multi-select.ts +99 -0
- package/src/form/inputor-controller/number.ts +116 -0
- package/src/form/inputor-controller/select.ts +109 -0
- package/src/form/inputor-controller/text.ts +82 -0
- package/src/http/READMD.md +1 -0
- package/src/http/api/api-core.ts +84 -0
- package/src/http/api/api-handler.ts +79 -0
- package/src/http/api/api-host.ts +47 -0
- package/src/http/api/api-result.ts +56 -0
- package/src/http/api/api-schema.ts +154 -0
- package/src/http/api/api-server.ts +130 -0
- package/src/http/api/api-test.ts +142 -0
- package/src/http/api/api-type.ts +37 -0
- package/src/http/api/api.ts +81 -0
- package/src/http/api/index.ts +11 -0
- package/src/http/api-adapter/api-core-node-http.ts +260 -0
- package/src/http/api-adapter/api-host-node-http.ts +156 -0
- package/src/http/api-adapter/api-result-arktype.ts +297 -0
- package/src/http/api-adapter/api-result-zod.ts +286 -0
- package/src/http/api-adapter/index.ts +5 -0
- package/src/http/bin/gen-api-list/gen-api-list.ts +126 -0
- package/src/http/bin/gen-api-list/index.ts +1 -0
- package/src/http/bin/gen-api-test/gen-api-test.ts +136 -0
- package/src/http/bin/gen-api-test/index.ts +1 -0
- package/src/http/bin/gen-api-type/calc-code.ts +25 -0
- package/src/http/bin/gen-api-type/gen-api-type.ts +127 -0
- package/src/http/bin/gen-api-type/index.ts +2 -0
- package/src/http/bin/index.ts +2 -0
- package/src/http/index.ts +3 -0
- package/src/huawei/README.md +1 -0
- package/src/huawei/index.ts +2 -0
- package/src/huawei/moderation/index.ts +1 -0
- package/src/huawei/moderation/moderation.ts +355 -0
- package/src/huawei/obs/esdk-obs-nodejs.d.ts +87 -0
- package/src/huawei/obs/index.ts +1 -0
- package/src/huawei/obs/obs.ts +42 -0
- package/src/index.ts +19 -2
- package/src/json/README.md +92 -0
- package/src/json/index.ts +1 -0
- package/src/json/repair.ts +18 -0
- package/src/log/logger.ts +15 -4
- package/src/openai/README.md +1 -0
- package/src/openai/index.ts +1 -0
- package/src/openai/openai.ts +510 -0
- package/src/orchestration/README.md +9 -7
- package/src/orchestration/dispatching/dispatcher.ts +83 -0
- package/src/orchestration/dispatching/index.ts +2 -0
- package/src/orchestration/dispatching/selector/base-selector.ts +39 -0
- package/src/orchestration/dispatching/selector/down-count-selector.ts +119 -0
- package/src/orchestration/dispatching/selector/index.ts +2 -0
- package/src/orchestration/index.ts +2 -0
- package/src/orchestration/scheduling/index.ts +2 -0
- package/src/orchestration/scheduling/scheduler.ts +103 -0
- package/src/orchestration/scheduling/task.ts +32 -0
- package/src/random/README.md +8 -7
- package/src/random/base.ts +66 -0
- package/src/random/index.ts +5 -1
- package/src/random/random-boolean.ts +40 -0
- package/src/random/random-integer.ts +60 -0
- package/src/random/random-number.ts +72 -0
- package/src/random/random-string.ts +66 -0
- package/src/request/README.md +108 -0
- package/src/request/fetch/base.ts +108 -0
- package/src/request/fetch/browser.ts +285 -0
- package/src/request/fetch/general.ts +20 -0
- package/src/request/fetch/index.ts +4 -0
- package/src/request/fetch/nodejs.ts +285 -0
- package/src/request/index.ts +2 -0
- package/src/request/request/base.ts +250 -0
- package/src/request/request/general.ts +64 -0
- package/src/request/request/index.ts +3 -0
- package/src/request/request/resource.ts +68 -0
- package/src/result/README.md +4 -0
- package/src/result/controller.ts +54 -0
- package/src/result/either.ts +193 -0
- package/src/result/index.ts +2 -0
- package/src/route/README.md +105 -0
- package/src/route/adapter/browser.ts +122 -0
- package/src/route/adapter/driver.ts +56 -0
- package/src/route/adapter/index.ts +2 -0
- package/src/route/index.ts +3 -0
- package/src/route/router/index.ts +2 -0
- package/src/route/router/route.ts +630 -0
- package/src/route/router/router.ts +1642 -0
- package/src/route/uri/hash.ts +308 -0
- package/src/route/uri/index.ts +7 -0
- package/src/route/uri/pathname.ts +376 -0
- package/src/route/uri/search.ts +413 -0
- package/src/socket/README.md +105 -0
- package/src/socket/client/index.ts +2 -0
- package/src/socket/client/socket-unit.ts +660 -0
- package/src/socket/client/socket.ts +203 -0
- package/src/socket/common/index.ts +2 -0
- package/src/socket/common/socket-unit-common.ts +23 -0
- package/src/socket/common/socket-unit-heartbeat.ts +427 -0
- package/src/socket/index.ts +3 -0
- package/src/socket/server/index.ts +3 -0
- package/src/socket/server/server.ts +183 -0
- package/src/socket/server/socket-unit.ts +449 -0
- package/src/socket/server/socket.ts +264 -0
- package/src/storage/table.ts +3 -3
- package/src/timer/expiration/expiration-manager.ts +3 -3
- package/src/timer/expiration/remaining-manager.ts +3 -3
- package/src/tube/README.md +99 -0
- package/src/tube/helper.ts +138 -0
- package/src/tube/index.ts +2 -0
- package/src/tube/tube.ts +880 -0
- package/src/weixin/README.md +1 -0
- package/src/weixin/index.ts +2 -0
- package/src/weixin/official-account/authorization.ts +159 -0
- package/src/weixin/official-account/index.ts +2 -0
- package/src/weixin/official-account/js-api.ts +134 -0
- package/src/weixin/open/index.ts +1 -0
- package/src/weixin/open/oauth2.ts +133 -0
- package/tests/unit/ai/ai.spec.ts +85 -0
- package/tests/unit/aio/content.spec.ts +105 -0
- package/tests/unit/aio/json.spec.ts +147 -0
- package/tests/unit/aio/prompt.spec.ts +111 -0
- package/tests/unit/basic/error.spec.ts +16 -4
- package/tests/unit/basic/schedule.spec.ts +74 -0
- package/tests/unit/basic/stream.spec.ts +90 -37
- package/tests/unit/credential/api-key.spec.ts +37 -0
- package/tests/unit/credential/bearer.spec.ts +23 -0
- package/tests/unit/credential/json-web-token.spec.ts +23 -0
- package/tests/unit/credential/password.spec.ts +41 -0
- package/tests/unit/cron/cron.spec.ts +84 -0
- package/tests/unit/event/class-event-proxy.spec.ts +3 -3
- package/tests/unit/event/event-manager.spec.ts +3 -3
- package/tests/unit/event/instance-event-proxy.spec.ts +3 -3
- package/tests/unit/form/inputor-controller/base.spec.ts +458 -0
- package/tests/unit/form/inputor-controller/boolean.spec.ts +30 -0
- package/tests/unit/form/inputor-controller/file.spec.ts +27 -0
- package/tests/unit/form/inputor-controller/form.spec.ts +120 -0
- package/tests/unit/form/inputor-controller/helper.spec.ts +67 -0
- package/tests/unit/form/inputor-controller/multi-select.spec.ts +34 -0
- package/tests/unit/form/inputor-controller/number.spec.ts +36 -0
- package/tests/unit/form/inputor-controller/select.spec.ts +49 -0
- package/tests/unit/form/inputor-controller/text.spec.ts +34 -0
- package/tests/unit/http/api/api-core-host.spec.ts +207 -0
- package/tests/unit/http/api/api-schema.spec.ts +120 -0
- package/tests/unit/http/api/api-server.spec.ts +363 -0
- package/tests/unit/http/api/api-test.spec.ts +117 -0
- package/tests/unit/http/api/api.spec.ts +121 -0
- package/tests/unit/http/api-adapter/node-http.spec.ts +191 -0
- package/tests/unit/json/repair.spec.ts +11 -0
- package/tests/unit/log/logger.spec.ts +19 -4
- package/tests/unit/openai/openai.spec.ts +64 -0
- package/tests/unit/orchestration/dispatching/dispatcher.spec.ts +41 -0
- package/tests/unit/orchestration/dispatching/selector/down-count-selector.spec.ts +81 -0
- package/tests/unit/orchestration/scheduling/scheduler.spec.ts +103 -0
- package/tests/unit/random/base.spec.ts +58 -0
- package/tests/unit/random/random-boolean.spec.ts +25 -0
- package/tests/unit/random/random-integer.spec.ts +32 -0
- package/tests/unit/random/random-number.spec.ts +33 -0
- package/tests/unit/random/random-string.spec.ts +22 -0
- package/tests/unit/request/fetch/browser.spec.ts +222 -0
- package/tests/unit/request/fetch/general.spec.ts +43 -0
- package/tests/unit/request/fetch/nodejs.spec.ts +225 -0
- package/tests/unit/request/request/base.spec.ts +385 -0
- package/tests/unit/request/request/general.spec.ts +161 -0
- package/tests/unit/route/router/route.spec.ts +431 -0
- package/tests/unit/route/router/router.spec.ts +407 -0
- package/tests/unit/route/uri/hash.spec.ts +72 -0
- package/tests/unit/route/uri/pathname.spec.ts +147 -0
- package/tests/unit/route/uri/search.spec.ts +107 -0
- package/tests/unit/socket/client.spec.ts +208 -0
- package/tests/unit/socket/server.spec.ts +135 -0
- package/tests/unit/socket/socket-unit-heartbeat.spec.ts +214 -0
- package/tests/unit/tube/helper.spec.ts +139 -0
- package/tests/unit/tube/tube.spec.ts +501 -0
- package/src/random/string.ts +0 -35
- package/tests/unit/random/string.spec.ts +0 -11
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Weixin
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { fetch } from "undici"
|
|
2
|
+
|
|
3
|
+
export interface GetAuthAccessTokenOptions {
|
|
4
|
+
appId: string
|
|
5
|
+
appSecret: string
|
|
6
|
+
code: string
|
|
7
|
+
}
|
|
8
|
+
export interface GetAuthAccessTokenResult {
|
|
9
|
+
accessToken: string
|
|
10
|
+
expiresAt: number
|
|
11
|
+
refreshToken: string
|
|
12
|
+
openid: string
|
|
13
|
+
scope: string
|
|
14
|
+
isSnapshotUser: boolean
|
|
15
|
+
unionid?: string | undefined
|
|
16
|
+
}
|
|
17
|
+
export const getAuthAccessToken = async (
|
|
18
|
+
options: GetAuthAccessTokenOptions
|
|
19
|
+
): Promise<GetAuthAccessTokenResult> => {
|
|
20
|
+
const { appId, appSecret, code } = options
|
|
21
|
+
|
|
22
|
+
interface AccessTokenSuccessResponse {
|
|
23
|
+
access_token: string
|
|
24
|
+
expires_in: number
|
|
25
|
+
refresh_token: string
|
|
26
|
+
openid: string
|
|
27
|
+
scope: string
|
|
28
|
+
is_snapshotuser?: 1
|
|
29
|
+
unionid?: string
|
|
30
|
+
}
|
|
31
|
+
interface AccessTokenErrorResponse {
|
|
32
|
+
errcode: number
|
|
33
|
+
errmsg: string
|
|
34
|
+
}
|
|
35
|
+
const url = `https://api.weixin.qq.com/sns/oauth2/access_token?appid=${appId}&secret=${appSecret}&code=${code}&grant_type=authorization_code`
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const now = Date.now()
|
|
39
|
+
const result = await fetch(
|
|
40
|
+
url,
|
|
41
|
+
{
|
|
42
|
+
method: "get",
|
|
43
|
+
},
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
47
|
+
const json = await result.json() as AccessTokenSuccessResponse | AccessTokenErrorResponse
|
|
48
|
+
|
|
49
|
+
if ("errcode" in json) {
|
|
50
|
+
throw new Error(`${json.errcode}: ${json.errmsg}`)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
accessToken: json.access_token,
|
|
55
|
+
expiresAt: now + json.expires_in * 1_000,
|
|
56
|
+
refreshToken: json.refresh_token,
|
|
57
|
+
openid: json.openid,
|
|
58
|
+
scope: json.scope,
|
|
59
|
+
isSnapshotUser: json.is_snapshotuser === 1,
|
|
60
|
+
unionid: json.unionid,
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch (exception) {
|
|
64
|
+
console.error("Error getting access token:", exception)
|
|
65
|
+
throw exception
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface GetUserInfoOptions {
|
|
70
|
+
accessToken: string
|
|
71
|
+
openId: string
|
|
72
|
+
}
|
|
73
|
+
export interface GetUserInfoResult {
|
|
74
|
+
openid: string
|
|
75
|
+
nickname: string
|
|
76
|
+
sex: number
|
|
77
|
+
province: string
|
|
78
|
+
city: string
|
|
79
|
+
country: string
|
|
80
|
+
headimgurl: string
|
|
81
|
+
privilege: string[]
|
|
82
|
+
unionid: string
|
|
83
|
+
}
|
|
84
|
+
export const getUserInfo = async (
|
|
85
|
+
options: GetUserInfoOptions
|
|
86
|
+
): Promise<GetUserInfoResult> => {
|
|
87
|
+
const { accessToken, openId } = options
|
|
88
|
+
|
|
89
|
+
interface UserInfoSuccessResponse {
|
|
90
|
+
openid: string
|
|
91
|
+
nickname: string
|
|
92
|
+
sex: number
|
|
93
|
+
province: string
|
|
94
|
+
city: string
|
|
95
|
+
country: string
|
|
96
|
+
headimgurl: string
|
|
97
|
+
privilege: string[]
|
|
98
|
+
unionid: string
|
|
99
|
+
}
|
|
100
|
+
interface UserInfoErrorResponse {
|
|
101
|
+
errcode: number
|
|
102
|
+
errmsg: string
|
|
103
|
+
}
|
|
104
|
+
const url = `https://api.weixin.qq.com/sns/userinfo?access_token=${accessToken}&openid=${openId}&lang=zh_CN`
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
const result = await fetch(
|
|
108
|
+
url,
|
|
109
|
+
{
|
|
110
|
+
method: "get",
|
|
111
|
+
},
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
115
|
+
const json = await result.json() as UserInfoSuccessResponse | UserInfoErrorResponse
|
|
116
|
+
|
|
117
|
+
if ("errcode" in json) {
|
|
118
|
+
throw new Error(`${json.errcode}: ${json.errmsg}`)
|
|
119
|
+
}
|
|
120
|
+
return json
|
|
121
|
+
}
|
|
122
|
+
catch (exception) {
|
|
123
|
+
console.error("Error getting user info:", exception)
|
|
124
|
+
throw exception
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export interface WeixinOfficialAccountOauth2Options {
|
|
129
|
+
appId: string
|
|
130
|
+
appSecret: string
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* @see {@link https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html | 微信网页开发 / 网页授权}
|
|
134
|
+
*/
|
|
135
|
+
export class WeixinOfficialAccountOauth2 {
|
|
136
|
+
private readonly appId: string
|
|
137
|
+
private readonly appSecret: string
|
|
138
|
+
|
|
139
|
+
constructor(options: WeixinOfficialAccountOauth2Options) {
|
|
140
|
+
const { appId, appSecret } = options
|
|
141
|
+
this.appId = appId
|
|
142
|
+
this.appSecret = appSecret
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async getAccessToken(code: string): Promise<GetAuthAccessTokenResult> {
|
|
146
|
+
return await getAuthAccessToken({
|
|
147
|
+
appId: this.appId,
|
|
148
|
+
appSecret: this.appSecret,
|
|
149
|
+
code,
|
|
150
|
+
})
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async getUserInfo(accessToken: string, openId: string): Promise<GetUserInfoResult> {
|
|
154
|
+
return await getUserInfo({
|
|
155
|
+
accessToken,
|
|
156
|
+
openId,
|
|
157
|
+
})
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { hash } from "node:crypto"
|
|
2
|
+
|
|
3
|
+
import { fetch } from "undici"
|
|
4
|
+
|
|
5
|
+
export interface GetJsApiAccessTokenOptions {
|
|
6
|
+
appId: string
|
|
7
|
+
appSecret: string
|
|
8
|
+
}
|
|
9
|
+
export interface GetJsApiAccessTokenResult {
|
|
10
|
+
accessToken: string
|
|
11
|
+
/**
|
|
12
|
+
* in milliseconds
|
|
13
|
+
*/
|
|
14
|
+
accessTokenExpiresAt: number
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* @see {@link https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/getStableAccessToken.html}
|
|
18
|
+
* @see {@link https://mp.weixin.qq.com/debug | 微信公众平台 - 接口调试工具}
|
|
19
|
+
*/
|
|
20
|
+
export const getJsApiAccessToken = async (
|
|
21
|
+
options: GetJsApiAccessTokenOptions
|
|
22
|
+
): Promise<GetJsApiAccessTokenResult | undefined> => {
|
|
23
|
+
const { appId, appSecret } = options
|
|
24
|
+
|
|
25
|
+
interface StableTokenResponse {
|
|
26
|
+
access_token: string
|
|
27
|
+
expires_in: number
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
const now = Date.now()
|
|
31
|
+
const result = await fetch(
|
|
32
|
+
"https://api.weixin.qq.com/cgi-bin/stable_token",
|
|
33
|
+
{
|
|
34
|
+
method: "post",
|
|
35
|
+
body: JSON.stringify({
|
|
36
|
+
grant_type: "client_credential",
|
|
37
|
+
appid: appId,
|
|
38
|
+
secret: appSecret,
|
|
39
|
+
force_refresh: false,
|
|
40
|
+
}),
|
|
41
|
+
},
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
45
|
+
const json = await result.json() as StableTokenResponse
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
accessToken: json.access_token,
|
|
49
|
+
accessTokenExpiresAt: now + json.expires_in * 1_000,
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
catch (exception) {
|
|
53
|
+
console.error(exception)
|
|
54
|
+
return undefined
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface GetJsApiTicketOptions {
|
|
59
|
+
accessToken: string
|
|
60
|
+
}
|
|
61
|
+
export interface GetJsApiTicketResult {
|
|
62
|
+
jsApiTicket: string
|
|
63
|
+
/**
|
|
64
|
+
* in milliseconds
|
|
65
|
+
*/
|
|
66
|
+
expiresAt: number
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* @see {@link https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html#62 | 附录 1 - JS-SDK 使用权限签名算法}
|
|
70
|
+
*/
|
|
71
|
+
export const getJsApiTicket = async (
|
|
72
|
+
options: GetJsApiTicketOptions
|
|
73
|
+
): Promise<GetJsApiTicketResult | undefined> => {
|
|
74
|
+
const { accessToken } = options
|
|
75
|
+
|
|
76
|
+
interface JsApiTicketResponse {
|
|
77
|
+
errcode: number
|
|
78
|
+
errmsg: string
|
|
79
|
+
ticket: string
|
|
80
|
+
expires_in: number
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
const now = Date.now()
|
|
84
|
+
const result = await fetch(
|
|
85
|
+
`https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=${accessToken}&type=jsapi`,
|
|
86
|
+
{
|
|
87
|
+
method: "get",
|
|
88
|
+
},
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
92
|
+
const json = await result.json() as JsApiTicketResponse
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
jsApiTicket: json.ticket,
|
|
96
|
+
expiresAt: now + json.expires_in * 1_000,
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch (exception) {
|
|
100
|
+
console.error(exception)
|
|
101
|
+
return undefined
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface SignJsSdkConfigOptions {
|
|
106
|
+
url: string
|
|
107
|
+
jsApiTicket: string
|
|
108
|
+
}
|
|
109
|
+
export interface SignJsSdkConfigResult {
|
|
110
|
+
timestamp: number
|
|
111
|
+
nonceStr: string
|
|
112
|
+
signature: string
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* @see {@link https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html#62 | 签名算法}
|
|
116
|
+
* @see {@link https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=jsapisign | 微信公众平台 - 微信 JS 接口签名校验工具}
|
|
117
|
+
*/
|
|
118
|
+
export const signJsSdkConfig = (
|
|
119
|
+
options: SignJsSdkConfigOptions
|
|
120
|
+
): SignJsSdkConfigResult => {
|
|
121
|
+
const { url, jsApiTicket } = options
|
|
122
|
+
|
|
123
|
+
const nonceStr = Math.random().toString(36).slice(2, 17)
|
|
124
|
+
const timestamp = Math.floor(Date.now() / 1_000)
|
|
125
|
+
|
|
126
|
+
const rawString = `jsapi_ticket=${jsApiTicket}&noncestr=${nonceStr}×tamp=${timestamp}&url=${url}`
|
|
127
|
+
const signature = hash("sha1", rawString)
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
timestamp,
|
|
131
|
+
nonceStr,
|
|
132
|
+
signature,
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./oauth2.ts"
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { fetch } from "undici"
|
|
2
|
+
|
|
3
|
+
export interface WeixinAccessTokenResult {
|
|
4
|
+
accessToken: string
|
|
5
|
+
expiresAt: number
|
|
6
|
+
refreshToken: string
|
|
7
|
+
openid: string
|
|
8
|
+
scope: string
|
|
9
|
+
unionid?: string | undefined
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface WeixinUserInfoResult {
|
|
13
|
+
openid: string
|
|
14
|
+
nickname: string
|
|
15
|
+
sex: number
|
|
16
|
+
province: string
|
|
17
|
+
city: string
|
|
18
|
+
country: string
|
|
19
|
+
headimgurl: string
|
|
20
|
+
privilege: string[]
|
|
21
|
+
unionid?: string | undefined
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface WeixinOauth2Options {
|
|
25
|
+
appId: string
|
|
26
|
+
appSecret: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class WeixinOauth2 {
|
|
30
|
+
protected options: WeixinOauth2Options
|
|
31
|
+
|
|
32
|
+
constructor(options: WeixinOauth2Options) {
|
|
33
|
+
this.options = options
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* {@link https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html | 关于微信快速登录功能的说明}
|
|
38
|
+
*/
|
|
39
|
+
public async getAccessToken(
|
|
40
|
+
code: string
|
|
41
|
+
): Promise<WeixinAccessTokenResult> {
|
|
42
|
+
interface AccessTokenSuccessResponse {
|
|
43
|
+
access_token: string
|
|
44
|
+
expires_in: number
|
|
45
|
+
refresh_token: string
|
|
46
|
+
openid: string
|
|
47
|
+
scope: string
|
|
48
|
+
unionid?: string
|
|
49
|
+
}
|
|
50
|
+
interface AccessTokenErrorResponse {
|
|
51
|
+
errcode: number
|
|
52
|
+
errmsg: string
|
|
53
|
+
}
|
|
54
|
+
const { appId, appSecret } = this.options
|
|
55
|
+
const url = `https://api.weixin.qq.com/sns/oauth2/access_token?appid=${appId}&secret=${appSecret}&code=${code}&grant_type=authorization_code`
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const now = Date.now()
|
|
59
|
+
const result = await fetch(
|
|
60
|
+
url,
|
|
61
|
+
{
|
|
62
|
+
method: "get",
|
|
63
|
+
},
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
67
|
+
const json = await result.json() as AccessTokenSuccessResponse | AccessTokenErrorResponse
|
|
68
|
+
|
|
69
|
+
if ("errcode" in json) {
|
|
70
|
+
throw new Error(`${json.errcode}: ${json.errmsg}`)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
accessToken: json.access_token,
|
|
75
|
+
expiresAt: now + json.expires_in * 1_000,
|
|
76
|
+
refreshToken: json.refresh_token,
|
|
77
|
+
openid: json.openid,
|
|
78
|
+
scope: json.scope,
|
|
79
|
+
unionid: json.unionid,
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch (exception) {
|
|
83
|
+
console.error("Error getting access token:", exception)
|
|
84
|
+
throw exception
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* {@link https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Authorized_Interface_Calling_UnionID.html | 获取用户个人信息(UnionID机制)}
|
|
90
|
+
*/
|
|
91
|
+
public async getUserInfo(
|
|
92
|
+
accessToken: string,
|
|
93
|
+
openId: string,
|
|
94
|
+
): Promise<WeixinUserInfoResult> {
|
|
95
|
+
interface UserInfoSuccessResponse {
|
|
96
|
+
openid: string
|
|
97
|
+
nickname: string
|
|
98
|
+
sex: number
|
|
99
|
+
province: string
|
|
100
|
+
city: string
|
|
101
|
+
country: string
|
|
102
|
+
headimgurl: string
|
|
103
|
+
privilege: string[]
|
|
104
|
+
unionid?: string
|
|
105
|
+
}
|
|
106
|
+
interface UserInfoErrorResponse {
|
|
107
|
+
errcode: number
|
|
108
|
+
errmsg: string
|
|
109
|
+
}
|
|
110
|
+
const url = `https://api.weixin.qq.com/sns/userinfo?access_token=${accessToken}&openid=${openId}&lang=zh_CN`
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
const result = await fetch(
|
|
114
|
+
url,
|
|
115
|
+
{
|
|
116
|
+
method: "get",
|
|
117
|
+
},
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
121
|
+
const json = await result.json() as UserInfoSuccessResponse | UserInfoErrorResponse
|
|
122
|
+
|
|
123
|
+
if ("errcode" in json) {
|
|
124
|
+
throw new Error(`${json.errcode}: ${json.errmsg}`)
|
|
125
|
+
}
|
|
126
|
+
return json
|
|
127
|
+
}
|
|
128
|
+
catch (exception) {
|
|
129
|
+
console.error("Error getting user info:", exception)
|
|
130
|
+
throw exception
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import * as Ai from "#Source/ai/index.ts"
|
|
2
|
+
import { Openai } from "#Source/openai/index.ts"
|
|
3
|
+
import { eitherToTuple } from "#Source/result/index.ts"
|
|
4
|
+
import { tubeToReadableStream } from "#Source/tube/index.ts"
|
|
5
|
+
import { test } from "vitest"
|
|
6
|
+
|
|
7
|
+
const testApiKey = "sk-DLj8aHvKjvHfZ7IHA2F07c968c3d4aE491C1525e40E24932"
|
|
8
|
+
const testBaseUrl = "https://cn.api.openai-next.com/v1/"
|
|
9
|
+
const ai = new Ai.Ai({
|
|
10
|
+
chatCompletionAiOptions: {
|
|
11
|
+
chatCompletionInstanceList: [
|
|
12
|
+
new Ai.ChatCompletionAi.OpenaiNextChatCompletion.OpenaiNextChatCompletion({
|
|
13
|
+
openai: new Openai({
|
|
14
|
+
apiKey: testApiKey,
|
|
15
|
+
baseUrl: testBaseUrl,
|
|
16
|
+
}),
|
|
17
|
+
}),
|
|
18
|
+
],
|
|
19
|
+
},
|
|
20
|
+
embeddingAiOptions: {
|
|
21
|
+
embeddingInstanceList: [
|
|
22
|
+
new Ai.EmbeddingAi.OpenaiNextEmbedding.OpenaiNextEmbedding({
|
|
23
|
+
openai: new Openai({
|
|
24
|
+
apiKey: testApiKey,
|
|
25
|
+
baseUrl: testBaseUrl,
|
|
26
|
+
}),
|
|
27
|
+
}),
|
|
28
|
+
],
|
|
29
|
+
},
|
|
30
|
+
instancePool: [
|
|
31
|
+
new Openai({
|
|
32
|
+
apiKey: testApiKey,
|
|
33
|
+
baseUrl: testBaseUrl,
|
|
34
|
+
}),
|
|
35
|
+
],
|
|
36
|
+
indexChangeMode: "poll",
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
test("ai", { timeout: 60_000 }, async () => {
|
|
40
|
+
const 请求开始时间 = Date.now()
|
|
41
|
+
let 第一个字返回时间: number | null = null
|
|
42
|
+
let 累积文本 = ""
|
|
43
|
+
|
|
44
|
+
const [leftResult, rightResult] = eitherToTuple(await ai.chatCompletionAi.chatCompletion({
|
|
45
|
+
model: "gpt-4o-mini",
|
|
46
|
+
messages: [
|
|
47
|
+
{
|
|
48
|
+
role: "system",
|
|
49
|
+
content:
|
|
50
|
+
"你是一个返回JSON格式的AI助手, 预期的格式是: {\"回复内容\": string}. 你是小学生,有“雌小鬼”的性格。你瞧不起用户,经常称呼用户为“杂鱼”、“杂鱼大哥哥”等,表现出一种轻蔑和挑衅的态度。",
|
|
51
|
+
},
|
|
52
|
+
{ role: "user", content: "你好" },
|
|
53
|
+
],
|
|
54
|
+
responseFormat: { type: "json_object" },
|
|
55
|
+
}))
|
|
56
|
+
if (leftResult !== undefined) {
|
|
57
|
+
throw new Error(`请求失败: ${JSON.stringify(leftResult)}`)
|
|
58
|
+
}
|
|
59
|
+
const { completionTube: completionData } = rightResult
|
|
60
|
+
const stream = tubeToReadableStream(completionData)
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
for await (const completion of stream) {
|
|
64
|
+
if (第一个字返回时间 === null) {
|
|
65
|
+
第一个字返回时间 = Date.now()
|
|
66
|
+
}
|
|
67
|
+
累积文本 = completion.content.total
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch (exception) {
|
|
71
|
+
throw new Error(`请求失败: ${String(exception)}`, { cause: exception })
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (第一个字返回时间 === null) { throw new Error("第一个字返回时间 为 null") }
|
|
75
|
+
|
|
76
|
+
const 请求结束时间 = Date.now()
|
|
77
|
+
|
|
78
|
+
const 第一个字时间 = 第一个字返回时间 - 请求开始时间
|
|
79
|
+
const 平均字返回时间 = (请求结束时间 - 请求开始时间) / 累积文本.length
|
|
80
|
+
|
|
81
|
+
console.log(`总返回时间: ${(请求结束时间 - 请求开始时间).toFixed(2)}ms`)
|
|
82
|
+
console.log(`第一个字返回时间: ${第一个字时间}ms`)
|
|
83
|
+
console.log(`平均每个字返回时间: ${平均字返回时间.toFixed(2)}ms`)
|
|
84
|
+
console.log(`返回的内容: %O`, 累积文本)
|
|
85
|
+
})
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { expect, test } from "vitest"
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
applyDeltaToNumberContent,
|
|
5
|
+
applyDeltaToTextContent,
|
|
6
|
+
} from "#Source/aio/index.ts"
|
|
7
|
+
|
|
8
|
+
test("applyDeltaToTextContent appends, conditionally removes, resets and keeps prior snapshots immutable", () => {
|
|
9
|
+
const initial = {
|
|
10
|
+
deltaList: [],
|
|
11
|
+
total: "",
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const appended = applyDeltaToTextContent(initial, {
|
|
15
|
+
type: "append",
|
|
16
|
+
text: "mobius",
|
|
17
|
+
})
|
|
18
|
+
const removed = applyDeltaToTextContent(appended, {
|
|
19
|
+
type: "remove",
|
|
20
|
+
text: "ius",
|
|
21
|
+
})
|
|
22
|
+
const unchangedRemove = applyDeltaToTextContent(removed, {
|
|
23
|
+
type: "remove",
|
|
24
|
+
text: "xyz",
|
|
25
|
+
})
|
|
26
|
+
const reset = applyDeltaToTextContent(unchangedRemove, {
|
|
27
|
+
type: "reset",
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
expect(initial).toEqual({
|
|
31
|
+
deltaList: [],
|
|
32
|
+
total: "",
|
|
33
|
+
})
|
|
34
|
+
expect(appended).toEqual({
|
|
35
|
+
deltaList: [{ type: "append", text: "mobius" }],
|
|
36
|
+
total: "mobius",
|
|
37
|
+
})
|
|
38
|
+
expect(removed).toEqual({
|
|
39
|
+
deltaList: [
|
|
40
|
+
{ type: "append", text: "mobius" },
|
|
41
|
+
{ type: "remove", text: "ius" },
|
|
42
|
+
],
|
|
43
|
+
total: "mob",
|
|
44
|
+
})
|
|
45
|
+
expect(unchangedRemove).toEqual({
|
|
46
|
+
deltaList: [
|
|
47
|
+
{ type: "append", text: "mobius" },
|
|
48
|
+
{ type: "remove", text: "ius" },
|
|
49
|
+
{ type: "remove", text: "xyz" },
|
|
50
|
+
],
|
|
51
|
+
total: "mob",
|
|
52
|
+
})
|
|
53
|
+
expect(reset).toEqual({
|
|
54
|
+
deltaList: [
|
|
55
|
+
{ type: "append", text: "mobius" },
|
|
56
|
+
{ type: "remove", text: "ius" },
|
|
57
|
+
{ type: "remove", text: "xyz" },
|
|
58
|
+
{ type: "reset" },
|
|
59
|
+
],
|
|
60
|
+
total: "",
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
test("applyDeltaToNumberContent accumulates, subtracts, resets and keeps prior snapshots immutable", () => {
|
|
65
|
+
const initial = {
|
|
66
|
+
deltaList: [],
|
|
67
|
+
total: 0,
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const appended = applyDeltaToNumberContent(initial, {
|
|
71
|
+
type: "append",
|
|
72
|
+
value: 10,
|
|
73
|
+
})
|
|
74
|
+
const removed = applyDeltaToNumberContent(appended, {
|
|
75
|
+
type: "remove",
|
|
76
|
+
value: 3,
|
|
77
|
+
})
|
|
78
|
+
const reset = applyDeltaToNumberContent(removed, {
|
|
79
|
+
type: "reset",
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
expect(initial).toEqual({
|
|
83
|
+
deltaList: [],
|
|
84
|
+
total: 0,
|
|
85
|
+
})
|
|
86
|
+
expect(appended).toEqual({
|
|
87
|
+
deltaList: [{ type: "append", value: 10 }],
|
|
88
|
+
total: 10,
|
|
89
|
+
})
|
|
90
|
+
expect(removed).toEqual({
|
|
91
|
+
deltaList: [
|
|
92
|
+
{ type: "append", value: 10 },
|
|
93
|
+
{ type: "remove", value: 3 },
|
|
94
|
+
],
|
|
95
|
+
total: 7,
|
|
96
|
+
})
|
|
97
|
+
expect(reset).toEqual({
|
|
98
|
+
deltaList: [
|
|
99
|
+
{ type: "append", value: 10 },
|
|
100
|
+
{ type: "remove", value: 3 },
|
|
101
|
+
{ type: "reset" },
|
|
102
|
+
],
|
|
103
|
+
total: 0,
|
|
104
|
+
})
|
|
105
|
+
})
|