@f3liz/rescript-misskey-api 0.6.9 → 0.8.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/lib/es6/src/Misskey.mjs +472 -159
- package/lib/es6/src/bindings/Ofetch.mjs +2 -0
- package/lib/es6/src/generated/kokonect-link/api/KokonectLinkAccount.mjs +70 -48
- package/lib/es6/src/generated/kokonect-link/api/KokonectLinkAdmin.mjs +133 -98
- package/lib/es6/src/generated/kokonect-link/api/KokonectLinkAntennas.mjs +9 -6
- package/lib/es6/src/generated/kokonect-link/api/KokonectLinkApp.mjs +6 -4
- package/lib/es6/src/generated/kokonect-link/api/KokonectLinkAuth.mjs +6 -4
- package/lib/es6/src/generated/kokonect-link/api/KokonectLinkChannels.mjs +18 -12
- package/lib/es6/src/generated/kokonect-link/api/KokonectLinkCharts.mjs +6 -4
- package/lib/es6/src/generated/kokonect-link/api/KokonectLinkChat.mjs +70 -54
- package/lib/es6/src/generated/kokonect-link/api/KokonectLinkClips.mjs +12 -8
- package/lib/es6/src/generated/kokonect-link/api/KokonectLinkDefault.mjs +27 -20
- package/lib/es6/src/generated/kokonect-link/api/KokonectLinkDrive.mjs +35 -24
- package/lib/es6/src/generated/kokonect-link/api/KokonectLinkFederation.mjs +18 -12
- package/lib/es6/src/generated/kokonect-link/api/KokonectLinkFlash.mjs +9 -6
- package/lib/es6/src/generated/kokonect-link/api/KokonectLinkFlashs.mjs +3 -2
- package/lib/es6/src/generated/kokonect-link/api/KokonectLinkFollowing.mjs +21 -14
- package/lib/es6/src/generated/kokonect-link/api/KokonectLinkGallery.mjs +12 -8
- package/lib/es6/src/generated/kokonect-link/api/KokonectLinkGroups.mjs +30 -24
- package/lib/es6/src/generated/kokonect-link/api/KokonectLinkHashtags.mjs +6 -4
- package/lib/es6/src/generated/kokonect-link/api/KokonectLinkLists.mjs +9 -6
- package/lib/es6/src/generated/kokonect-link/api/KokonectLinkMeta.mjs +27 -18
- package/lib/es6/src/generated/kokonect-link/api/KokonectLinkNotes.mjs +85 -58
- package/lib/es6/src/generated/kokonect-link/api/KokonectLinkNotifications.mjs +2 -2
- package/lib/es6/src/generated/kokonect-link/api/KokonectLinkPages.mjs +8 -6
- package/lib/es6/src/generated/kokonect-link/api/KokonectLinkRole.mjs +9 -6
- package/lib/es6/src/generated/kokonect-link/api/KokonectLinkUsers.mjs +35 -24
- package/lib/es6/src/generated/kokonect-link/api/KokonectLinkWebhooks.mjs +7 -6
- package/lib/es6/src/generated/misskey-io/api/MisskeyIoAccount.mjs +92 -68
- package/lib/es6/src/generated/misskey-io/api/MisskeyIoAdmin.mjs +266 -214
- package/lib/es6/src/generated/misskey-io/api/MisskeyIoAntennas.mjs +17 -12
- package/lib/es6/src/generated/misskey-io/api/MisskeyIoApp.mjs +6 -4
- package/lib/es6/src/generated/misskey-io/api/MisskeyIoAuth.mjs +14 -10
- package/lib/es6/src/generated/misskey-io/api/MisskeyIoChannels.mjs +35 -26
- package/lib/es6/src/generated/misskey-io/api/MisskeyIoCharts.mjs +72 -48
- package/lib/es6/src/generated/misskey-io/api/MisskeyIoClip.mjs +4 -4
- package/lib/es6/src/generated/misskey-io/api/MisskeyIoClips.mjs +17 -12
- package/lib/es6/src/generated/misskey-io/api/MisskeyIoDefault.mjs +133 -110
- package/lib/es6/src/generated/misskey-io/api/MisskeyIoDrive.mjs +51 -36
- package/lib/es6/src/generated/misskey-io/api/MisskeyIoFederation.mjs +32 -22
- package/lib/es6/src/generated/misskey-io/api/MisskeyIoFlash.mjs +12 -10
- package/lib/es6/src/generated/misskey-io/api/MisskeyIoFlashs.mjs +5 -4
- package/lib/es6/src/generated/misskey-io/api/MisskeyIoFollowing.mjs +27 -20
- package/lib/es6/src/generated/misskey-io/api/MisskeyIoGallery.mjs +24 -18
- package/lib/es6/src/generated/misskey-io/api/MisskeyIoHashtags.mjs +18 -12
- package/lib/es6/src/generated/misskey-io/api/MisskeyIoLists.mjs +23 -18
- package/lib/es6/src/generated/misskey-io/api/MisskeyIoMeta.mjs +71 -48
- package/lib/es6/src/generated/misskey-io/api/MisskeyIoNonProductive.mjs +5 -4
- package/lib/es6/src/generated/misskey-io/api/MisskeyIoNotes.mjs +96 -70
- package/lib/es6/src/generated/misskey-io/api/MisskeyIoNotifications.mjs +8 -8
- package/lib/es6/src/generated/misskey-io/api/MisskeyIoPages.mjs +17 -14
- package/lib/es6/src/generated/misskey-io/api/MisskeyIoReactions.mjs +4 -4
- package/lib/es6/src/generated/misskey-io/api/MisskeyIoResetPassword.mjs +4 -4
- package/lib/es6/src/generated/misskey-io/api/MisskeyIoRole.mjs +12 -8
- package/lib/es6/src/generated/misskey-io/api/MisskeyIoUsers.mjs +83 -56
- package/lib/es6/src/generated/misskey-io/api/MisskeyIoWebhooks.mjs +17 -14
- package/package.json +3 -2
- package/src/Misskey.res +423 -151
- package/src/bindings/Ofetch.res +14 -0
- package/src/generated/kokonect-link/api/KokonectLinkAccount.res +94 -166
- package/src/generated/kokonect-link/api/KokonectLinkAdmin.res +182 -329
- package/src/generated/kokonect-link/api/KokonectLinkAntennas.res +12 -21
- package/src/generated/kokonect-link/api/KokonectLinkApp.res +8 -14
- package/src/generated/kokonect-link/api/KokonectLinkAuth.res +8 -14
- package/src/generated/kokonect-link/api/KokonectLinkChannels.res +24 -42
- package/src/generated/kokonect-link/api/KokonectLinkCharts.res +8 -14
- package/src/generated/kokonect-link/api/KokonectLinkChat.res +97 -178
- package/src/generated/kokonect-link/api/KokonectLinkClips.res +16 -28
- package/src/generated/kokonect-link/api/KokonectLinkDefault.res +37 -67
- package/src/generated/kokonect-link/api/KokonectLinkDrive.res +47 -83
- package/src/generated/kokonect-link/api/KokonectLinkFederation.res +24 -42
- package/src/generated/kokonect-link/api/KokonectLinkFlash.res +12 -21
- package/src/generated/kokonect-link/api/KokonectLinkFlashs.res +4 -7
- package/src/generated/kokonect-link/api/KokonectLinkFollowing.res +28 -49
- package/src/generated/kokonect-link/api/KokonectLinkGallery.res +16 -28
- package/src/generated/kokonect-link/api/KokonectLinkGroups.res +42 -78
- package/src/generated/kokonect-link/api/KokonectLinkHashtags.res +8 -14
- package/src/generated/kokonect-link/api/KokonectLinkLists.res +12 -21
- package/src/generated/kokonect-link/api/KokonectLinkMeta.res +36 -63
- package/src/generated/kokonect-link/api/KokonectLinkNotes.res +114 -201
- package/src/generated/kokonect-link/api/KokonectLinkNotifications.res +3 -6
- package/src/generated/kokonect-link/api/KokonectLinkPages.res +11 -20
- package/src/generated/kokonect-link/api/KokonectLinkRole.res +12 -21
- package/src/generated/kokonect-link/api/KokonectLinkUsers.res +47 -83
- package/src/generated/kokonect-link/api/KokonectLinkWebhooks.res +10 -19
- package/src/generated/misskey-io/api/MisskeyIoAccount.res +126 -228
- package/src/generated/misskey-io/api/MisskeyIoAdmin.res +373 -694
- package/src/generated/misskey-io/api/MisskeyIoAntennas.res +23 -41
- package/src/generated/misskey-io/api/MisskeyIoApp.res +8 -14
- package/src/generated/misskey-io/api/MisskeyIoAuth.res +19 -34
- package/src/generated/misskey-io/api/MisskeyIoChannels.res +48 -87
- package/src/generated/misskey-io/api/MisskeyIoCharts.res +96 -168
- package/src/generated/misskey-io/api/MisskeyIoClip.res +6 -12
- package/src/generated/misskey-io/api/MisskeyIoClips.res +23 -41
- package/src/generated/misskey-io/api/MisskeyIoDefault.res +188 -353
- package/src/generated/misskey-io/api/MisskeyIoDrive.res +69 -123
- package/src/generated/misskey-io/api/MisskeyIoFederation.res +43 -76
- package/src/generated/misskey-io/api/MisskeyIoFlash.res +17 -32
- package/src/generated/misskey-io/api/MisskeyIoFlashs.res +7 -13
- package/src/generated/misskey-io/api/MisskeyIoFollowing.res +37 -67
- package/src/generated/misskey-io/api/MisskeyIoGallery.res +33 -60
- package/src/generated/misskey-io/api/MisskeyIoHashtags.res +24 -42
- package/src/generated/misskey-io/api/MisskeyIoLists.res +32 -59
- package/src/generated/misskey-io/api/MisskeyIoMeta.res +95 -167
- package/src/generated/misskey-io/api/MisskeyIoNonProductive.res +7 -13
- package/src/generated/misskey-io/api/MisskeyIoNotes.res +131 -236
- package/src/generated/misskey-io/api/MisskeyIoNotifications.res +12 -24
- package/src/generated/misskey-io/api/MisskeyIoPages.res +24 -45
- package/src/generated/misskey-io/api/MisskeyIoReactions.res +6 -12
- package/src/generated/misskey-io/api/MisskeyIoResetPassword.res +6 -12
- package/src/generated/misskey-io/api/MisskeyIoRole.res +16 -28
- package/src/generated/misskey-io/api/MisskeyIoUsers.res +111 -195
- package/src/generated/misskey-io/api/MisskeyIoWebhooks.res +24 -45
package/src/Misskey.res
CHANGED
|
@@ -18,30 +18,9 @@
|
|
|
18
18
|
// You can override it by passing ~fetch to connect() for custom behavior
|
|
19
19
|
// (e.g., adding performance metrics, retries, logging, etc.).
|
|
20
20
|
|
|
21
|
-
// ============================================================================
|
|
22
|
-
// Fetch Bindings
|
|
23
|
-
// ============================================================================
|
|
24
|
-
|
|
25
21
|
// Enable JSON schema for Sury
|
|
26
22
|
S.enableJson()
|
|
27
23
|
|
|
28
|
-
module FetchBindings = {
|
|
29
|
-
type response
|
|
30
|
-
|
|
31
|
-
type requestInit = {
|
|
32
|
-
method: string,
|
|
33
|
-
headers: dict<string>,
|
|
34
|
-
body: option<string>,
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
@val external fetch: (string, requestInit) => promise<response> = "fetch"
|
|
38
|
-
|
|
39
|
-
@send external json: response => promise<JSON.t> = "json"
|
|
40
|
-
@get external ok: response => bool = "ok"
|
|
41
|
-
@get external status: response => int = "status"
|
|
42
|
-
@get external statusText: response => string = "statusText"
|
|
43
|
-
}
|
|
44
|
-
|
|
45
24
|
// ============================================================================
|
|
46
25
|
// Client & Configuration
|
|
47
26
|
// ============================================================================
|
|
@@ -56,50 +35,34 @@ type t = {
|
|
|
56
35
|
mutable streamClient: option<StreamClient.t>,
|
|
57
36
|
}
|
|
58
37
|
|
|
59
|
-
//
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
// Misskey injects the token into the request body
|
|
67
|
-
let bodyWithToken = switch (body, token) {
|
|
68
|
-
| (Some(jsonBody), Some(t)) =>
|
|
69
|
-
switch jsonBody->JSON.Decode.object {
|
|
70
|
-
| Some(obj) =>
|
|
71
|
-
obj->Dict.set("i", t->JSON.Encode.string)
|
|
72
|
-
Some(obj->JSON.Encode.object)
|
|
73
|
-
| None => body
|
|
74
|
-
}
|
|
75
|
-
| (None, Some(t)) =>
|
|
76
|
-
let obj = Dict.make()
|
|
38
|
+
// Inject auth token into request body (Misskey API convention)
|
|
39
|
+
let injectToken = (body: option<JSON.t>, token: option<string>): option<JSON.t> => {
|
|
40
|
+
switch (body, token) {
|
|
41
|
+
| (Some(jsonBody), Some(t)) =>
|
|
42
|
+
switch jsonBody->JSON.Decode.object {
|
|
43
|
+
| Some(obj) =>
|
|
77
44
|
obj->Dict.set("i", t->JSON.Encode.string)
|
|
78
45
|
Some(obj->JSON.Encode.object)
|
|
79
|
-
|
|
|
46
|
+
| None => body
|
|
80
47
|
}
|
|
48
|
+
| (None, Some(t)) =>
|
|
49
|
+
let obj = Dict.make()
|
|
50
|
+
obj->Dict.set("i", t->JSON.Encode.string)
|
|
51
|
+
Some(obj->JSON.Encode.object)
|
|
52
|
+
| _ => body
|
|
53
|
+
}
|
|
54
|
+
}
|
|
81
55
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
body: bodyStr,
|
|
93
|
-
},
|
|
94
|
-
)->Promise.then(response => {
|
|
95
|
-
if FetchBindings.ok(response) {
|
|
96
|
-
response->FetchBindings.json
|
|
97
|
-
} else {
|
|
98
|
-
let msg = `API error: ${FetchBindings.status(
|
|
99
|
-
response,
|
|
100
|
-
)->Int.toString} ${FetchBindings.statusText(response)}`
|
|
101
|
-
Promise.reject(JsExn(JsError.make(msg)->Obj.magic))
|
|
102
|
-
}
|
|
56
|
+
// Default fetch implementation using ofetch.
|
|
57
|
+
// Uses baseURL for origin, auto JSON parsing, and token injection.
|
|
58
|
+
let defaultFetch = (~origin: string, ~token: option<string>) => {
|
|
59
|
+
(~url: string, ~method_: string, ~body: option<JSON.t>): Promise.t<JSON.t> => {
|
|
60
|
+
let bodyWithToken = injectToken(body, token)
|
|
61
|
+
Ofetch.ofetch(url, {
|
|
62
|
+
baseURL: `${origin}/api`,
|
|
63
|
+
method: method_,
|
|
64
|
+
body: ?bodyWithToken,
|
|
65
|
+
retry: 0,
|
|
103
66
|
})
|
|
104
67
|
}
|
|
105
68
|
}
|
|
@@ -143,21 +106,42 @@ let wrapperConnect = (client: t): MisskeyIoWrapper.client => {
|
|
|
143
106
|
///
|
|
144
107
|
/// Example:
|
|
145
108
|
/// let user = await client->Misskey.request("i", ())
|
|
146
|
-
let request = (
|
|
109
|
+
let request = async (
|
|
147
110
|
client: t,
|
|
148
111
|
endpoint: string,
|
|
149
112
|
~params: JSON.t=JSON.Encode.object(Dict.make()),
|
|
150
113
|
(),
|
|
151
|
-
):
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
114
|
+
): result<JSON.t, string> => {
|
|
115
|
+
try {
|
|
116
|
+
let json = await client.fetchFn(~url=endpoint, ~method_="POST", ~body=Some(params))
|
|
117
|
+
Ok(json)
|
|
118
|
+
} catch {
|
|
119
|
+
| err =>
|
|
120
|
+
// ofetch attaches response data to error.data for non-2xx responses
|
|
121
|
+
let errorData: option<JSON.t> = (err->Obj.magic)["data"]
|
|
122
|
+
let baseMsg = switch err->JsExn.fromException {
|
|
156
123
|
| Some(jsExn) => JsExn.message(jsExn)->Option.getOr("Unknown error")
|
|
157
124
|
| None => "Unknown error"
|
|
158
125
|
}
|
|
159
|
-
|
|
160
|
-
|
|
126
|
+
let msg = switch errorData {
|
|
127
|
+
| Some(data) =>
|
|
128
|
+
switch data->JSON.Decode.object {
|
|
129
|
+
| Some(obj) =>
|
|
130
|
+
switch obj->Dict.get("error")->Option.flatMap(JSON.Decode.object) {
|
|
131
|
+
| Some(errObj) =>
|
|
132
|
+
let code =
|
|
133
|
+
errObj->Dict.get("code")->Option.flatMap(JSON.Decode.string)->Option.getOr("")
|
|
134
|
+
let message =
|
|
135
|
+
errObj->Dict.get("message")->Option.flatMap(JSON.Decode.string)->Option.getOr("")
|
|
136
|
+
`${baseMsg} [${code}] ${message}`
|
|
137
|
+
| None => baseMsg
|
|
138
|
+
}
|
|
139
|
+
| None => baseMsg
|
|
140
|
+
}
|
|
141
|
+
| None => baseMsg
|
|
142
|
+
}
|
|
143
|
+
Error(msg)
|
|
144
|
+
}
|
|
161
145
|
}
|
|
162
146
|
|
|
163
147
|
/// Get current user info.
|
|
@@ -168,6 +152,9 @@ let currentUser = (client: t): promise<result<JSON.t, string>> => {
|
|
|
168
152
|
/// Get client origin (instance URL).
|
|
169
153
|
let origin = (client: t): string => client.origin
|
|
170
154
|
|
|
155
|
+
/// Get client authentication token.
|
|
156
|
+
let token = (client: t): option<string> => client.token
|
|
157
|
+
|
|
171
158
|
/// Close client and cleanup (close streaming connections).
|
|
172
159
|
let close = (client: t): unit => {
|
|
173
160
|
client.streamClient->Option.forEach(s => s->StreamClient.close)
|
|
@@ -200,6 +187,7 @@ module Notes = {
|
|
|
200
187
|
~localOnly: bool=false,
|
|
201
188
|
~replyId: option<string>=?,
|
|
202
189
|
~renoteId: option<string>=?,
|
|
190
|
+
~fileIds: option<array<string>>=?,
|
|
203
191
|
(),
|
|
204
192
|
): promise<result<JSON.t, string>> => {
|
|
205
193
|
let params = Dict.make()
|
|
@@ -217,6 +205,9 @@ module Notes = {
|
|
|
217
205
|
cw->Option.forEach(v => params->Dict.set("cw", v->JSON.Encode.string))
|
|
218
206
|
replyId->Option.forEach(v => params->Dict.set("replyId", v->JSON.Encode.string))
|
|
219
207
|
renoteId->Option.forEach(v => params->Dict.set("renoteId", v->JSON.Encode.string))
|
|
208
|
+
fileIds->Option.forEach(ids =>
|
|
209
|
+
params->Dict.set("fileIds", ids->Array.map(JSON.Encode.string)->JSON.Encode.array)
|
|
210
|
+
)
|
|
220
211
|
|
|
221
212
|
request(client, "notes/create", ~params=params->JSON.Encode.object, ())
|
|
222
213
|
}
|
|
@@ -292,12 +283,89 @@ module Notes = {
|
|
|
292
283
|
params->Dict.set("noteId", noteId->JSON.Encode.string)
|
|
293
284
|
request(client, "notes/reactions/delete", ~params=params->JSON.Encode.object, ())
|
|
294
285
|
}
|
|
286
|
+
|
|
287
|
+
/// Get a single note by ID.
|
|
288
|
+
let show = (client: t, noteId: string): promise<result<JSON.t, string>> => {
|
|
289
|
+
let params = Dict.make()
|
|
290
|
+
params->Dict.set("noteId", noteId->JSON.Encode.string)
|
|
291
|
+
request(client, "notes/show", ~params=params->JSON.Encode.object, ())
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/// Get replies/children of a note.
|
|
295
|
+
let children = (
|
|
296
|
+
client: t,
|
|
297
|
+
noteId: string,
|
|
298
|
+
~limit: int=30,
|
|
299
|
+
~sinceId: option<string>=?,
|
|
300
|
+
~untilId: option<string>=?,
|
|
301
|
+
(),
|
|
302
|
+
): promise<result<JSON.t, string>> => {
|
|
303
|
+
let params = Dict.make()
|
|
304
|
+
params->Dict.set("noteId", noteId->JSON.Encode.string)
|
|
305
|
+
params->Dict.set("limit", limit->JSON.Encode.int)
|
|
306
|
+
sinceId->Option.forEach(v => params->Dict.set("sinceId", v->JSON.Encode.string))
|
|
307
|
+
untilId->Option.forEach(v => params->Dict.set("untilId", v->JSON.Encode.string))
|
|
308
|
+
request(client, "notes/children", ~params=params->JSON.Encode.object, ())
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/// Get the conversation thread (parent notes) for a note.
|
|
312
|
+
let conversation = (
|
|
313
|
+
client: t,
|
|
314
|
+
noteId: string,
|
|
315
|
+
~limit: int=30,
|
|
316
|
+
(),
|
|
317
|
+
): promise<result<JSON.t, string>> => {
|
|
318
|
+
let params = Dict.make()
|
|
319
|
+
params->Dict.set("noteId", noteId->JSON.Encode.string)
|
|
320
|
+
params->Dict.set("limit", limit->JSON.Encode.int)
|
|
321
|
+
request(client, "notes/conversation", ~params=params->JSON.Encode.object, ())
|
|
322
|
+
}
|
|
295
323
|
}
|
|
296
324
|
|
|
297
325
|
// ============================================================================
|
|
298
|
-
//
|
|
326
|
+
// Users API
|
|
299
327
|
// ============================================================================
|
|
300
328
|
|
|
329
|
+
module Users = {
|
|
330
|
+
/// Get user profile by username (and optional host for remote users).
|
|
331
|
+
let show = (
|
|
332
|
+
client: t,
|
|
333
|
+
~userId: option<string>=?,
|
|
334
|
+
~username: option<string>=?,
|
|
335
|
+
~host: option<string>=?,
|
|
336
|
+
(),
|
|
337
|
+
): promise<result<JSON.t, string>> => {
|
|
338
|
+
let params = Dict.make()
|
|
339
|
+
userId->Option.forEach(v => params->Dict.set("userId", v->JSON.Encode.string))
|
|
340
|
+
username->Option.forEach(v => params->Dict.set("username", v->JSON.Encode.string))
|
|
341
|
+
host->Option.forEach(v => params->Dict.set("host", v->JSON.Encode.string))
|
|
342
|
+
request(client, "users/show", ~params=params->JSON.Encode.object, ())
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/// Get notes posted by a user.
|
|
346
|
+
let notes = (
|
|
347
|
+
client: t,
|
|
348
|
+
userId: string,
|
|
349
|
+
~limit: int=20,
|
|
350
|
+
~withReplies: bool=false,
|
|
351
|
+
~withRenotes: bool=true,
|
|
352
|
+
~withFiles: bool=false,
|
|
353
|
+
~sinceId: option<string>=?,
|
|
354
|
+
~untilId: option<string>=?,
|
|
355
|
+
(),
|
|
356
|
+
): promise<result<JSON.t, string>> => {
|
|
357
|
+
let params = Dict.make()
|
|
358
|
+
params->Dict.set("userId", userId->JSON.Encode.string)
|
|
359
|
+
params->Dict.set("limit", limit->JSON.Encode.int)
|
|
360
|
+
params->Dict.set("withReplies", withReplies->JSON.Encode.bool)
|
|
361
|
+
params->Dict.set("withRenotes", withRenotes->JSON.Encode.bool)
|
|
362
|
+
params->Dict.set("withFiles", withFiles->JSON.Encode.bool)
|
|
363
|
+
sinceId->Option.forEach(v => params->Dict.set("sinceId", v->JSON.Encode.string))
|
|
364
|
+
untilId->Option.forEach(v => params->Dict.set("untilId", v->JSON.Encode.string))
|
|
365
|
+
request(client, "users/notes", ~params=params->JSON.Encode.object, ())
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
301
369
|
module Stream = {
|
|
302
370
|
type subscription = {dispose: unit => unit}
|
|
303
371
|
|
|
@@ -413,21 +481,20 @@ module Emojis = {
|
|
|
413
481
|
}
|
|
414
482
|
|
|
415
483
|
/// Get list of custom emojis from instance.
|
|
416
|
-
let list = (client: t):
|
|
417
|
-
request(client, "emojis", ())
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
| None => Ok([])
|
|
425
|
-
}
|
|
484
|
+
let list = async (client: t): result<array<customEmoji>, string> => {
|
|
485
|
+
let result = await request(client, "emojis", ())
|
|
486
|
+
switch result {
|
|
487
|
+
| Ok(json) =>
|
|
488
|
+
switch json->JSON.Decode.object {
|
|
489
|
+
| Some(obj) =>
|
|
490
|
+
switch obj->Dict.get("emojis")->Option.flatMap(JSON.Decode.array) {
|
|
491
|
+
| Some(emojisArray) => Ok(emojisArray->Array.filterMap(decodeCustomEmoji))
|
|
426
492
|
| None => Ok([])
|
|
427
493
|
}
|
|
428
|
-
|
|
|
429
|
-
}
|
|
430
|
-
|
|
494
|
+
| None => Ok([])
|
|
495
|
+
}
|
|
496
|
+
| Error(e) => Error(e)
|
|
497
|
+
}
|
|
431
498
|
}
|
|
432
499
|
}
|
|
433
500
|
|
|
@@ -437,45 +504,42 @@ module Emojis = {
|
|
|
437
504
|
|
|
438
505
|
module CustomTimelines = {
|
|
439
506
|
/// Fetch user's antennas.
|
|
440
|
-
let antennas = (client: t):
|
|
441
|
-
request(client, "antennas/list", ())
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
})
|
|
507
|
+
let antennas = async (client: t): result<array<JSON.t>, string> => {
|
|
508
|
+
let result = await request(client, "antennas/list", ())
|
|
509
|
+
switch result {
|
|
510
|
+
| Ok(json) =>
|
|
511
|
+
switch json->JSON.Decode.array {
|
|
512
|
+
| Some(arr) => Ok(arr)
|
|
513
|
+
| None => Ok([])
|
|
514
|
+
}
|
|
515
|
+
| Error(e) => Error(e)
|
|
516
|
+
}
|
|
451
517
|
}
|
|
452
518
|
|
|
453
519
|
/// Fetch user's lists.
|
|
454
|
-
let lists = (client: t):
|
|
455
|
-
request(client, "users/lists/list", ())
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
})
|
|
520
|
+
let lists = async (client: t): result<array<JSON.t>, string> => {
|
|
521
|
+
let result = await request(client, "users/lists/list", ())
|
|
522
|
+
switch result {
|
|
523
|
+
| Ok(json) =>
|
|
524
|
+
switch json->JSON.Decode.array {
|
|
525
|
+
| Some(arr) => Ok(arr)
|
|
526
|
+
| None => Ok([])
|
|
527
|
+
}
|
|
528
|
+
| Error(e) => Error(e)
|
|
529
|
+
}
|
|
465
530
|
}
|
|
466
531
|
|
|
467
532
|
/// Fetch user's followed channels.
|
|
468
|
-
let channels = (client: t):
|
|
469
|
-
request(client, "channels/followed", ())
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
})
|
|
533
|
+
let channels = async (client: t): result<array<JSON.t>, string> => {
|
|
534
|
+
let result = await request(client, "channels/followed", ())
|
|
535
|
+
switch result {
|
|
536
|
+
| Ok(json) =>
|
|
537
|
+
switch json->JSON.Decode.array {
|
|
538
|
+
| Some(arr) => Ok(arr)
|
|
539
|
+
| None => Ok([])
|
|
540
|
+
}
|
|
541
|
+
| Error(e) => Error(e)
|
|
542
|
+
}
|
|
479
543
|
}
|
|
480
544
|
|
|
481
545
|
/// Extract ID and name from a timeline item JSON.
|
|
@@ -589,15 +653,10 @@ module MiAuth = {
|
|
|
589
653
|
}
|
|
590
654
|
|
|
591
655
|
@val external encodeURIComponent: string => string = "encodeURIComponent"
|
|
656
|
+
@val @scope("crypto") external randomUUID: unit => string = "randomUUID"
|
|
592
657
|
|
|
593
658
|
let generateSessionId = (): string => {
|
|
594
|
-
|
|
595
|
-
function() {
|
|
596
|
-
var a = new Uint8Array(16);
|
|
597
|
-
crypto.getRandomValues(a);
|
|
598
|
-
return Array.from(a, function(b) { return b.toString(16).padStart(2, '0'); }).join('');
|
|
599
|
-
}()
|
|
600
|
-
`)
|
|
659
|
+
randomUUID()->String.replaceAll("-", "")
|
|
601
660
|
}
|
|
602
661
|
|
|
603
662
|
/// Generate MiAuth URL for user authorization.
|
|
@@ -637,35 +696,22 @@ module MiAuth = {
|
|
|
637
696
|
/// authorization is complete, and `{ok: false}` when still pending.
|
|
638
697
|
let check = async (~origin: string, ~sessionId: string): result<checkResult, string> => {
|
|
639
698
|
try {
|
|
640
|
-
let
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
let json = await response->FetchBindings.json
|
|
655
|
-
switch json->JSON.Decode.object {
|
|
656
|
-
| Some(obj) =>
|
|
657
|
-
// Check the "ok" field in the response body
|
|
658
|
-
let isOk = obj->Dict.get("ok")->Option.flatMap(JSON.Decode.bool)->Option.getOr(false)
|
|
659
|
-
if isOk {
|
|
660
|
-
let token = obj->Dict.get("token")->Option.flatMap(JSON.Decode.string)
|
|
661
|
-
let user = obj->Dict.get("user")
|
|
662
|
-
Ok({token, user})
|
|
663
|
-
} else {
|
|
664
|
-
// Server explicitly says not authorized yet
|
|
665
|
-
Ok({token: None, user: None})
|
|
666
|
-
}
|
|
667
|
-
| None => Error("Unexpected response format")
|
|
699
|
+
let json = await Ofetch.ofetch(`${origin}/api/miauth/${sessionId}/check`, {
|
|
700
|
+
method: "POST",
|
|
701
|
+
body: JSON.Encode.object(Dict.make()),
|
|
702
|
+
retry: 0,
|
|
703
|
+
})
|
|
704
|
+
switch json->JSON.Decode.object {
|
|
705
|
+
| Some(obj) =>
|
|
706
|
+
let isOk = obj->Dict.get("ok")->Option.flatMap(JSON.Decode.bool)->Option.getOr(false)
|
|
707
|
+
if isOk {
|
|
708
|
+
let token = obj->Dict.get("token")->Option.flatMap(JSON.Decode.string)
|
|
709
|
+
let user = obj->Dict.get("user")
|
|
710
|
+
Ok({token, user})
|
|
711
|
+
} else {
|
|
712
|
+
Ok({token: None, user: None})
|
|
668
713
|
}
|
|
714
|
+
| None => Error("Unexpected response format")
|
|
669
715
|
}
|
|
670
716
|
} catch {
|
|
671
717
|
| err =>
|
|
@@ -717,6 +763,166 @@ let isPermissionDenied = (error: JSON.t): bool => {
|
|
|
717
763
|
}
|
|
718
764
|
|
|
719
765
|
/// Check if error is an API error and extract error info.
|
|
766
|
+
// ============================================================================
|
|
767
|
+
// Instance Meta API
|
|
768
|
+
// ============================================================================
|
|
769
|
+
|
|
770
|
+
module Meta = {
|
|
771
|
+
type meta = {
|
|
772
|
+
swPublickey: option<string>,
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
/// Get instance metadata (includes VAPID public key for push notifications).
|
|
776
|
+
let get = async (client: t): result<meta, string> => {
|
|
777
|
+
switch await request(client, "meta", ()) {
|
|
778
|
+
| Ok(json) =>
|
|
779
|
+
switch json->JSON.Decode.object {
|
|
780
|
+
| Some(obj) =>
|
|
781
|
+
Ok({
|
|
782
|
+
swPublickey: obj
|
|
783
|
+
->Dict.get("swPublickey")
|
|
784
|
+
->Option.flatMap(JSON.Decode.string),
|
|
785
|
+
})
|
|
786
|
+
| None => Error("Invalid meta response")
|
|
787
|
+
}
|
|
788
|
+
| Error(e) => Error(e)
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
// ============================================================================
|
|
794
|
+
// Service Worker (Push Notification) API
|
|
795
|
+
// ============================================================================
|
|
796
|
+
|
|
797
|
+
module Sw = {
|
|
798
|
+
type registration = {
|
|
799
|
+
state: option<string>,
|
|
800
|
+
key: option<string>,
|
|
801
|
+
userId: string,
|
|
802
|
+
endpoint: string,
|
|
803
|
+
sendReadMessage: bool,
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
/// Register a push notification endpoint with the Misskey instance.
|
|
807
|
+
let register = async (
|
|
808
|
+
client: t,
|
|
809
|
+
~endpoint: string,
|
|
810
|
+
~auth: string,
|
|
811
|
+
~publickey: string,
|
|
812
|
+
~sendReadMessage: bool=false,
|
|
813
|
+
(),
|
|
814
|
+
): result<registration, string> => {
|
|
815
|
+
let params = Dict.make()
|
|
816
|
+
params->Dict.set("endpoint", endpoint->JSON.Encode.string)
|
|
817
|
+
params->Dict.set("auth", auth->JSON.Encode.string)
|
|
818
|
+
params->Dict.set("publickey", publickey->JSON.Encode.string)
|
|
819
|
+
params->Dict.set("sendReadMessage", sendReadMessage->JSON.Encode.bool)
|
|
820
|
+
switch await request(client, "sw/register", ~params=params->JSON.Encode.object, ()) {
|
|
821
|
+
| Ok(json) =>
|
|
822
|
+
switch json->JSON.Decode.object {
|
|
823
|
+
| Some(obj) =>
|
|
824
|
+
Ok({
|
|
825
|
+
state: obj->Dict.get("state")->Option.flatMap(JSON.Decode.string),
|
|
826
|
+
key: obj->Dict.get("key")->Option.flatMap(JSON.Decode.string),
|
|
827
|
+
userId: obj->Dict.get("userId")->Option.flatMap(JSON.Decode.string)->Option.getOr(""),
|
|
828
|
+
endpoint: obj->Dict.get("endpoint")->Option.flatMap(JSON.Decode.string)->Option.getOr(""),
|
|
829
|
+
sendReadMessage: obj
|
|
830
|
+
->Dict.get("sendReadMessage")
|
|
831
|
+
->Option.flatMap(JSON.Decode.bool)
|
|
832
|
+
->Option.getOr(false),
|
|
833
|
+
})
|
|
834
|
+
| None => Error("Invalid sw/register response")
|
|
835
|
+
}
|
|
836
|
+
| Error(e) => Error(e)
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
/// Unregister a push notification endpoint.
|
|
841
|
+
let unregister = async (client: t, ~endpoint: string): result<unit, string> => {
|
|
842
|
+
let params = Dict.make()
|
|
843
|
+
params->Dict.set("endpoint", endpoint->JSON.Encode.string)
|
|
844
|
+
switch await request(client, "sw/unregister", ~params=params->JSON.Encode.object, ()) {
|
|
845
|
+
| Ok(_) => Ok()
|
|
846
|
+
| Error(e) => Error(e)
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
// ============================================================================
|
|
852
|
+
// Webhooks API
|
|
853
|
+
// ============================================================================
|
|
854
|
+
|
|
855
|
+
module Webhooks = {
|
|
856
|
+
type webhook = {
|
|
857
|
+
id: string,
|
|
858
|
+
name: string,
|
|
859
|
+
url: string,
|
|
860
|
+
active: bool,
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
/// Create a webhook on the Misskey instance.
|
|
864
|
+
let create = async (
|
|
865
|
+
client: t,
|
|
866
|
+
~name: string,
|
|
867
|
+
~url: string,
|
|
868
|
+
~secret: string,
|
|
869
|
+
~on: array<string>,
|
|
870
|
+
(),
|
|
871
|
+
): result<webhook, string> => {
|
|
872
|
+
let params = Dict.make()
|
|
873
|
+
params->Dict.set("name", name->JSON.Encode.string)
|
|
874
|
+
params->Dict.set("url", url->JSON.Encode.string)
|
|
875
|
+
params->Dict.set("secret", secret->JSON.Encode.string)
|
|
876
|
+
params->Dict.set("on", on->Array.map(JSON.Encode.string)->JSON.Encode.array)
|
|
877
|
+
switch await request(client, "i/webhooks/create", ~params=params->JSON.Encode.object, ()) {
|
|
878
|
+
| Ok(json) =>
|
|
879
|
+
switch json->JSON.Decode.object {
|
|
880
|
+
| Some(obj) =>
|
|
881
|
+
let id = obj->Dict.get("id")->Option.flatMap(JSON.Decode.string)->Option.getOr("")
|
|
882
|
+
let name = obj->Dict.get("name")->Option.flatMap(JSON.Decode.string)->Option.getOr("")
|
|
883
|
+
let url = obj->Dict.get("url")->Option.flatMap(JSON.Decode.string)->Option.getOr("")
|
|
884
|
+
let active = obj->Dict.get("active")->Option.flatMap(JSON.Decode.bool)->Option.getOr(false)
|
|
885
|
+
Ok({id, name, url, active})
|
|
886
|
+
| None => Error("Invalid webhook response")
|
|
887
|
+
}
|
|
888
|
+
| Error(e) => Error(e)
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
/// Delete a webhook by ID.
|
|
893
|
+
let delete = async (client: t, ~webhookId: string): result<unit, string> => {
|
|
894
|
+
let params = Dict.make()
|
|
895
|
+
params->Dict.set("webhookId", webhookId->JSON.Encode.string)
|
|
896
|
+
switch await request(client, "i/webhooks/delete", ~params=params->JSON.Encode.object, ()) {
|
|
897
|
+
| Ok(_) => Ok()
|
|
898
|
+
| Error(e) => Error(e)
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
/// List webhooks for the current user.
|
|
903
|
+
let list = async (client: t): result<array<webhook>, string> => {
|
|
904
|
+
switch await request(client, "i/webhooks/list", ()) {
|
|
905
|
+
| Ok(json) =>
|
|
906
|
+
switch json->JSON.Decode.array {
|
|
907
|
+
| Some(arr) =>
|
|
908
|
+
Ok(arr->Array.filterMap(item => {
|
|
909
|
+
switch item->JSON.Decode.object {
|
|
910
|
+
| Some(obj) =>
|
|
911
|
+
let id = obj->Dict.get("id")->Option.flatMap(JSON.Decode.string)->Option.getOr("")
|
|
912
|
+
let name = obj->Dict.get("name")->Option.flatMap(JSON.Decode.string)->Option.getOr("")
|
|
913
|
+
let url = obj->Dict.get("url")->Option.flatMap(JSON.Decode.string)->Option.getOr("")
|
|
914
|
+
let active = obj->Dict.get("active")->Option.flatMap(JSON.Decode.bool)->Option.getOr(false)
|
|
915
|
+
Some({id, name, url, active})
|
|
916
|
+
| None => None
|
|
917
|
+
}
|
|
918
|
+
}))
|
|
919
|
+
| None => Ok([])
|
|
920
|
+
}
|
|
921
|
+
| Error(e) => Error(e)
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
|
|
720
926
|
let isAPIError = (error: JSON.t): option<apiError> => {
|
|
721
927
|
switch error->JSON.Decode.object {
|
|
722
928
|
| Some(obj) =>
|
|
@@ -732,4 +938,70 @@ let isAPIError = (error: JSON.t): option<apiError> => {
|
|
|
732
938
|
}
|
|
733
939
|
}
|
|
734
940
|
|
|
941
|
+
// ============================================================================
|
|
942
|
+
// Drive API
|
|
943
|
+
// ============================================================================
|
|
944
|
+
// NOTE: All HTTP calls in this module (and throughout Misskey.res) should use
|
|
945
|
+
// `Ofetch.ofetch` rather than raw `fetch`. ofetch handles JSON parsing,
|
|
946
|
+
// non-2xx error throwing, and runtime FormData detection automatically, which
|
|
947
|
+
// avoids the pitfall of calling `response.json()` on non-standard objects.
|
|
948
|
+
|
|
949
|
+
module Drive = {
|
|
950
|
+
/// Upload a File object to the Misskey drive.
|
|
951
|
+
/// Returns the drive file ID on success.
|
|
952
|
+
let upload = async (client: t, ~file: {..}, ~sensitive: bool=false, ()): result<string, string> => {
|
|
953
|
+
try {
|
|
954
|
+
let fd: {..} = %raw(`new FormData()`)
|
|
955
|
+
fd["append"]("file", file)
|
|
956
|
+
client.token->Option.forEach(tok => fd["append"]("i", tok))
|
|
957
|
+
if sensitive { fd["append"]("isSensitive", "true") }
|
|
958
|
+
|
|
959
|
+
let endpoint = client.origin ++ "/api/drive/files/create"
|
|
960
|
+
// Cast FormData to JSON.t so ofetch can accept it;
|
|
961
|
+
// ofetch detects FormData at runtime and sends multipart/form-data correctly.
|
|
962
|
+
let json = await Ofetch.ofetch(endpoint, {
|
|
963
|
+
method: "POST",
|
|
964
|
+
body: fd->Obj.magic,
|
|
965
|
+
})
|
|
966
|
+
|
|
967
|
+
switch json->JSON.Decode.object->Option.flatMap(obj => obj->Dict.get("id")) {
|
|
968
|
+
| Some(idJson) =>
|
|
969
|
+
switch idJson->JSON.Decode.string {
|
|
970
|
+
| Some(id) => Ok(id)
|
|
971
|
+
| None => Error("Drive upload: id is not a string")
|
|
972
|
+
}
|
|
973
|
+
| None =>
|
|
974
|
+
let errDetail =
|
|
975
|
+
json
|
|
976
|
+
->JSON.Decode.object
|
|
977
|
+
->Option.flatMap(obj => obj->Dict.get("error"))
|
|
978
|
+
->Option.flatMap(JSON.Decode.object)
|
|
979
|
+
->Option.flatMap(obj => obj->Dict.get("message"))
|
|
980
|
+
->Option.flatMap(JSON.Decode.string)
|
|
981
|
+
let statusCode =
|
|
982
|
+
json
|
|
983
|
+
->JSON.Decode.object
|
|
984
|
+
->Option.flatMap(obj => obj->Dict.get("error"))
|
|
985
|
+
->Option.flatMap(JSON.Decode.object)
|
|
986
|
+
->Option.flatMap(obj => obj->Dict.get("code"))
|
|
987
|
+
->Option.flatMap(JSON.Decode.string)
|
|
988
|
+
let msg = switch (statusCode, errDetail) {
|
|
989
|
+
| (Some(code), Some(detail)) => code ++ ": " ++ detail
|
|
990
|
+
| (None, Some(detail)) => detail
|
|
991
|
+
| (Some(code), None) => "Upload error: " ++ code
|
|
992
|
+
| (None, None) => "Unknown upload error"
|
|
993
|
+
}
|
|
994
|
+
Error(msg)
|
|
995
|
+
}
|
|
996
|
+
} catch {
|
|
997
|
+
| err =>
|
|
998
|
+
let msg = switch err->JsExn.fromException {
|
|
999
|
+
| Some(jsExn) => JsExn.message(jsExn)->Option.getOr("Drive upload failed")
|
|
1000
|
+
| None => "Drive upload failed"
|
|
1001
|
+
}
|
|
1002
|
+
Error(msg)
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
|
|
735
1007
|
let default = connect
|