@honeybbq/teamspeak 0.0.1 → 1.0.0-beta.7

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.
Files changed (393) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +726 -2
  3. package/dist/client/abort.d.ts +5 -0
  4. package/dist/client/abort.d.ts.map +1 -0
  5. package/dist/client/abort.js +23 -0
  6. package/dist/client/abort.js.map +1 -0
  7. package/dist/client/address.d.ts +17 -0
  8. package/dist/client/address.d.ts.map +1 -0
  9. package/dist/client/address.js +66 -0
  10. package/dist/client/address.js.map +1 -0
  11. package/dist/client/api/voice.d.ts +24 -0
  12. package/dist/client/api/voice.d.ts.map +1 -0
  13. package/dist/client/api/voice.js +33 -0
  14. package/dist/client/api/voice.js.map +1 -0
  15. package/dist/client/capabilities.d.ts +68 -0
  16. package/dist/client/capabilities.d.ts.map +1 -0
  17. package/dist/client/capabilities.js +59 -0
  18. package/dist/client/capabilities.js.map +1 -0
  19. package/dist/client/client.d.ts +196 -0
  20. package/dist/client/client.d.ts.map +1 -0
  21. package/dist/client/client.js +1523 -0
  22. package/dist/client/client.js.map +1 -0
  23. package/dist/client/commands.d.ts +66 -0
  24. package/dist/client/commands.d.ts.map +1 -0
  25. package/dist/client/commands.js +162 -0
  26. package/dist/client/commands.js.map +1 -0
  27. package/dist/client/crypto/crypt-ops.d.ts +7 -0
  28. package/dist/client/crypto/crypt-ops.d.ts.map +1 -0
  29. package/dist/client/crypto/crypt-ops.js +2 -0
  30. package/dist/client/crypto/crypt-ops.js.map +1 -0
  31. package/dist/client/crypto/crypt.d.ts +37 -0
  32. package/dist/client/crypto/crypt.d.ts.map +1 -0
  33. package/dist/client/crypto/crypt.js +203 -0
  34. package/dist/client/crypto/crypt.js.map +1 -0
  35. package/dist/client/crypto/eax.d.ts +12 -0
  36. package/dist/client/crypto/eax.d.ts.map +1 -0
  37. package/dist/client/crypto/eax.js +118 -0
  38. package/dist/client/crypto/eax.js.map +1 -0
  39. package/dist/client/crypto/identity.d.ts +97 -0
  40. package/dist/client/crypto/identity.d.ts.map +1 -0
  41. package/dist/client/crypto/identity.js +412 -0
  42. package/dist/client/crypto/identity.js.map +1 -0
  43. package/dist/client/crypto/index.d.ts +7 -0
  44. package/dist/client/crypto/index.d.ts.map +1 -0
  45. package/dist/client/crypto/index.js +5 -0
  46. package/dist/client/crypto/index.js.map +1 -0
  47. package/dist/client/crypto/primitives.d.ts +43 -0
  48. package/dist/client/crypto/primitives.d.ts.map +1 -0
  49. package/dist/client/crypto/primitives.js +116 -0
  50. package/dist/client/crypto/primitives.js.map +1 -0
  51. package/dist/client/diagnostics.d.ts +45 -0
  52. package/dist/client/diagnostics.d.ts.map +1 -0
  53. package/dist/client/diagnostics.js +30 -0
  54. package/dist/client/diagnostics.js.map +1 -0
  55. package/dist/client/discovery/index.d.ts +2 -0
  56. package/dist/client/discovery/index.d.ts.map +1 -0
  57. package/dist/client/discovery/index.js +2 -0
  58. package/dist/client/discovery/index.js.map +1 -0
  59. package/dist/client/discovery/resolver.d.ts +26 -0
  60. package/dist/client/discovery/resolver.d.ts.map +1 -0
  61. package/dist/client/discovery/resolver.js +275 -0
  62. package/dist/client/discovery/resolver.js.map +1 -0
  63. package/dist/client/errors.d.ts +82 -0
  64. package/dist/client/errors.d.ts.map +1 -0
  65. package/dist/client/errors.js +201 -0
  66. package/dist/client/errors.js.map +1 -0
  67. package/dist/client/events.d.ts +3 -0
  68. package/dist/client/events.d.ts.map +1 -0
  69. package/dist/client/events.js +8 -0
  70. package/dist/client/events.js.map +1 -0
  71. package/dist/client/handshake/crypt-handshake.d.ts +17 -0
  72. package/dist/client/handshake/crypt-handshake.d.ts.map +1 -0
  73. package/dist/client/handshake/crypt-handshake.js +128 -0
  74. package/dist/client/handshake/crypt-handshake.js.map +1 -0
  75. package/dist/client/handshake/crypt-init2.d.ts +7 -0
  76. package/dist/client/handshake/crypt-init2.d.ts.map +1 -0
  77. package/dist/client/handshake/crypt-init2.js +37 -0
  78. package/dist/client/handshake/crypt-init2.js.map +1 -0
  79. package/dist/client/handshake/index.d.ts +4 -0
  80. package/dist/client/handshake/index.d.ts.map +1 -0
  81. package/dist/client/handshake/index.js +4 -0
  82. package/dist/client/handshake/index.js.map +1 -0
  83. package/dist/client/handshake/license.d.ts +35 -0
  84. package/dist/client/handshake/license.d.ts.map +1 -0
  85. package/dist/client/handshake/license.js +169 -0
  86. package/dist/client/handshake/license.js.map +1 -0
  87. package/dist/client/handshake.d.ts +27 -0
  88. package/dist/client/handshake.d.ts.map +1 -0
  89. package/dist/client/handshake.js +126 -0
  90. package/dist/client/handshake.js.map +1 -0
  91. package/dist/client/helpers.d.ts +6 -0
  92. package/dist/client/helpers.d.ts.map +1 -0
  93. package/dist/client/helpers.js +13 -0
  94. package/dist/client/helpers.js.map +1 -0
  95. package/dist/client/hooks.d.ts +7 -0
  96. package/dist/client/hooks.d.ts.map +1 -0
  97. package/dist/client/hooks.js +28 -0
  98. package/dist/client/hooks.js.map +1 -0
  99. package/dist/client/index.d.ts +26 -0
  100. package/dist/client/index.d.ts.map +1 -0
  101. package/dist/client/index.js +28 -0
  102. package/dist/client/index.js.map +1 -0
  103. package/dist/client/reconnect.d.ts +12 -0
  104. package/dist/client/reconnect.d.ts.map +1 -0
  105. package/dist/client/reconnect.js +44 -0
  106. package/dist/client/reconnect.js.map +1 -0
  107. package/dist/client/throttle.d.ts +20 -0
  108. package/dist/client/throttle.d.ts.map +1 -0
  109. package/dist/client/throttle.js +83 -0
  110. package/dist/client/throttle.js.map +1 -0
  111. package/dist/client/trace.d.ts +223 -0
  112. package/dist/client/trace.d.ts.map +1 -0
  113. package/dist/client/trace.js +13 -0
  114. package/dist/client/trace.js.map +1 -0
  115. package/dist/client/transfer.d.ts +111 -0
  116. package/dist/client/transfer.d.ts.map +1 -0
  117. package/dist/client/transfer.js +243 -0
  118. package/dist/client/transfer.js.map +1 -0
  119. package/dist/client/transport/generation-window.d.ts +30 -0
  120. package/dist/client/transport/generation-window.d.ts.map +1 -0
  121. package/dist/client/transport/generation-window.js +97 -0
  122. package/dist/client/transport/generation-window.js.map +1 -0
  123. package/dist/client/transport/handler.d.ts +61 -0
  124. package/dist/client/transport/handler.d.ts.map +1 -0
  125. package/dist/client/transport/handler.js +793 -0
  126. package/dist/client/transport/handler.js.map +1 -0
  127. package/dist/client/transport/index.d.ts +6 -0
  128. package/dist/client/transport/index.d.ts.map +1 -0
  129. package/dist/client/transport/index.js +5 -0
  130. package/dist/client/transport/index.js.map +1 -0
  131. package/dist/client/transport/packet.d.ts +36 -0
  132. package/dist/client/transport/packet.d.ts.map +1 -0
  133. package/dist/client/transport/packet.js +55 -0
  134. package/dist/client/transport/packet.js.map +1 -0
  135. package/dist/client/transport/quicklz.d.ts +24 -0
  136. package/dist/client/transport/quicklz.d.ts.map +1 -0
  137. package/dist/client/transport/quicklz.js +179 -0
  138. package/dist/client/transport/quicklz.js.map +1 -0
  139. package/dist/client/types.d.ts +226 -0
  140. package/dist/client/types.d.ts.map +1 -0
  141. package/dist/client/types.js +39 -0
  142. package/dist/client/types.js.map +1 -0
  143. package/dist/core/api/channels.d.ts +182 -0
  144. package/dist/core/api/channels.d.ts.map +1 -0
  145. package/dist/core/api/channels.js +155 -0
  146. package/dist/core/api/channels.js.map +1 -0
  147. package/dist/core/api/clients.d.ts +304 -0
  148. package/dist/core/api/clients.d.ts.map +1 -0
  149. package/dist/core/api/clients.js +260 -0
  150. package/dist/core/api/clients.js.map +1 -0
  151. package/dist/core/api/command.d.ts +305 -0
  152. package/dist/core/api/command.d.ts.map +1 -0
  153. package/dist/core/api/command.js +363 -0
  154. package/dist/core/api/command.js.map +1 -0
  155. package/dist/core/api/files.d.ts +153 -0
  156. package/dist/core/api/files.d.ts.map +1 -0
  157. package/dist/core/api/files.js +181 -0
  158. package/dist/core/api/files.js.map +1 -0
  159. package/dist/core/api/groups.d.ts +248 -0
  160. package/dist/core/api/groups.d.ts.map +1 -0
  161. package/dist/core/api/groups.js +271 -0
  162. package/dist/core/api/groups.js.map +1 -0
  163. package/dist/core/api/moderation.d.ts +205 -0
  164. package/dist/core/api/moderation.d.ts.map +1 -0
  165. package/dist/core/api/moderation.js +209 -0
  166. package/dist/core/api/moderation.js.map +1 -0
  167. package/dist/core/api/parse.d.ts +16 -0
  168. package/dist/core/api/parse.d.ts.map +1 -0
  169. package/dist/core/api/parse.js +40 -0
  170. package/dist/core/api/parse.js.map +1 -0
  171. package/dist/core/api/server.d.ts +310 -0
  172. package/dist/core/api/server.d.ts.map +1 -0
  173. package/dist/core/api/server.js +290 -0
  174. package/dist/core/api/server.js.map +1 -0
  175. package/dist/core/command/command.d.ts +20 -0
  176. package/dist/core/command/command.d.ts.map +1 -0
  177. package/dist/core/command/command.js +112 -0
  178. package/dist/core/command/command.js.map +1 -0
  179. package/dist/core/command/ids.d.ts +125 -0
  180. package/dist/core/command/ids.d.ts.map +1 -0
  181. package/dist/core/command/ids.js +85 -0
  182. package/dist/core/command/ids.js.map +1 -0
  183. package/dist/core/command/parser.d.ts +17 -0
  184. package/dist/core/command/parser.d.ts.map +1 -0
  185. package/dist/core/command/parser.js +65 -0
  186. package/dist/core/command/parser.js.map +1 -0
  187. package/dist/core/command/request.d.ts +61 -0
  188. package/dist/core/command/request.d.ts.map +1 -0
  189. package/dist/core/command/request.js +64 -0
  190. package/dist/core/command/request.js.map +1 -0
  191. package/dist/core/command/responses.d.ts +59 -0
  192. package/dist/core/command/responses.d.ts.map +1 -0
  193. package/dist/core/command/responses.js +74 -0
  194. package/dist/core/command/responses.js.map +1 -0
  195. package/dist/core/command-name.d.ts +333 -0
  196. package/dist/core/command-name.d.ts.map +1 -0
  197. package/dist/core/command-name.js +331 -0
  198. package/dist/core/command-name.js.map +1 -0
  199. package/dist/core/enums.d.ts +146 -0
  200. package/dist/core/enums.d.ts.map +1 -0
  201. package/dist/core/enums.js +132 -0
  202. package/dist/core/enums.js.map +1 -0
  203. package/dist/core/errorcode.d.ts +413 -0
  204. package/dist/core/errorcode.d.ts.map +1 -0
  205. package/dist/core/errorcode.js +410 -0
  206. package/dist/core/errorcode.js.map +1 -0
  207. package/dist/core/errors.d.ts +111 -0
  208. package/dist/core/errors.d.ts.map +1 -0
  209. package/dist/core/errors.js +233 -0
  210. package/dist/core/errors.js.map +1 -0
  211. package/dist/core/events.d.ts +207 -0
  212. package/dist/core/events.d.ts.map +1 -0
  213. package/dist/core/events.js +6 -0
  214. package/dist/core/events.js.map +1 -0
  215. package/dist/core/helpers.d.ts +42 -0
  216. package/dist/core/helpers.d.ts.map +1 -0
  217. package/dist/core/helpers.js +156 -0
  218. package/dist/core/helpers.js.map +1 -0
  219. package/dist/core/index.d.ts +35 -0
  220. package/dist/core/index.d.ts.map +1 -0
  221. package/dist/core/index.js +34 -0
  222. package/dist/core/index.js.map +1 -0
  223. package/dist/core/internal.d.ts +23 -0
  224. package/dist/core/internal.d.ts.map +1 -0
  225. package/dist/core/internal.js +20 -0
  226. package/dist/core/internal.js.map +1 -0
  227. package/dist/core/middleware.d.ts +14 -0
  228. package/dist/core/middleware.d.ts.map +1 -0
  229. package/dist/core/middleware.js +18 -0
  230. package/dist/core/middleware.js.map +1 -0
  231. package/dist/core/notification-name.d.ts +154 -0
  232. package/dist/core/notification-name.d.ts.map +1 -0
  233. package/dist/core/notification-name.js +152 -0
  234. package/dist/core/notification-name.js.map +1 -0
  235. package/dist/core/notifications.d.ts +43 -0
  236. package/dist/core/notifications.d.ts.map +1 -0
  237. package/dist/core/notifications.js +116 -0
  238. package/dist/core/notifications.js.map +1 -0
  239. package/dist/core/permission-name.d.ts +533 -0
  240. package/dist/core/permission-name.d.ts.map +1 -0
  241. package/dist/core/permission-name.js +531 -0
  242. package/dist/core/permission-name.js.map +1 -0
  243. package/dist/core/permission.d.ts +31 -0
  244. package/dist/core/permission.d.ts.map +1 -0
  245. package/dist/core/permission.js +20 -0
  246. package/dist/core/permission.js.map +1 -0
  247. package/dist/core/property-name.d.ts +403 -0
  248. package/dist/core/property-name.d.ts.map +1 -0
  249. package/dist/core/property-name.js +405 -0
  250. package/dist/core/property-name.js.map +1 -0
  251. package/dist/core/reason.d.ts +39 -0
  252. package/dist/core/reason.d.ts.map +1 -0
  253. package/dist/core/reason.js +36 -0
  254. package/dist/core/reason.js.map +1 -0
  255. package/dist/core/reconnect.d.ts +59 -0
  256. package/dist/core/reconnect.d.ts.map +1 -0
  257. package/dist/core/reconnect.js +55 -0
  258. package/dist/core/reconnect.js.map +1 -0
  259. package/dist/core/types.d.ts +110 -0
  260. package/dist/core/types.d.ts.map +1 -0
  261. package/dist/core/types.js +27 -0
  262. package/dist/core/types.js.map +1 -0
  263. package/dist/query/client.d.ts +194 -0
  264. package/dist/query/client.d.ts.map +1 -0
  265. package/dist/query/client.js +967 -0
  266. package/dist/query/client.js.map +1 -0
  267. package/dist/query/errors.d.ts +61 -0
  268. package/dist/query/errors.d.ts.map +1 -0
  269. package/dist/query/errors.js +100 -0
  270. package/dist/query/errors.js.map +1 -0
  271. package/dist/query/index.d.ts +36 -0
  272. package/dist/query/index.d.ts.map +1 -0
  273. package/dist/query/index.js +34 -0
  274. package/dist/query/index.js.map +1 -0
  275. package/dist/query/knownhosts.d.ts +61 -0
  276. package/dist/query/knownhosts.d.ts.map +1 -0
  277. package/dist/query/knownhosts.js +223 -0
  278. package/dist/query/knownhosts.js.map +1 -0
  279. package/dist/query/lines.d.ts +14 -0
  280. package/dist/query/lines.d.ts.map +1 -0
  281. package/dist/query/lines.js +29 -0
  282. package/dist/query/lines.js.map +1 -0
  283. package/dist/query/options.d.ts +187 -0
  284. package/dist/query/options.d.ts.map +1 -0
  285. package/dist/query/options.js +213 -0
  286. package/dist/query/options.js.map +1 -0
  287. package/dist/query/reconnect.d.ts +29 -0
  288. package/dist/query/reconnect.d.ts.map +1 -0
  289. package/dist/query/reconnect.js +87 -0
  290. package/dist/query/reconnect.js.map +1 -0
  291. package/dist/query/ssh.d.ts +345 -0
  292. package/dist/query/ssh.d.ts.map +1 -0
  293. package/dist/query/ssh.js +241 -0
  294. package/dist/query/ssh.js.map +1 -0
  295. package/dist/query/stream.d.ts +38 -0
  296. package/dist/query/stream.d.ts.map +1 -0
  297. package/dist/query/stream.js +70 -0
  298. package/dist/query/stream.js.map +1 -0
  299. package/dist/query/tcp.d.ts +38 -0
  300. package/dist/query/tcp.d.ts.map +1 -0
  301. package/dist/query/tcp.js +53 -0
  302. package/dist/query/tcp.js.map +1 -0
  303. package/dist/query/transport.d.ts +36 -0
  304. package/dist/query/transport.d.ts.map +1 -0
  305. package/dist/query/transport.js +2 -0
  306. package/dist/query/transport.js.map +1 -0
  307. package/dist/query/web.d.ts +54 -0
  308. package/dist/query/web.d.ts.map +1 -0
  309. package/dist/query/web.js +146 -0
  310. package/dist/query/web.js.map +1 -0
  311. package/dist/versions/index.d.ts +3094 -0
  312. package/dist/versions/index.d.ts.map +1 -0
  313. package/dist/versions/index.js +9237 -0
  314. package/dist/versions/index.js.map +1 -0
  315. package/package.json +118 -4
  316. package/src/client/abort.ts +24 -0
  317. package/src/client/address.ts +74 -0
  318. package/src/client/api/voice.ts +61 -0
  319. package/src/client/capabilities.ts +106 -0
  320. package/src/client/client.ts +1809 -0
  321. package/src/client/commands.ts +193 -0
  322. package/src/client/crypto/crypt-ops.ts +6 -0
  323. package/src/client/crypto/crypt.ts +275 -0
  324. package/src/client/crypto/eax.ts +152 -0
  325. package/src/client/crypto/identity.ts +484 -0
  326. package/src/client/crypto/index.ts +21 -0
  327. package/src/client/crypto/primitives.ts +141 -0
  328. package/src/client/diagnostics.ts +58 -0
  329. package/src/client/discovery/index.ts +1 -0
  330. package/src/client/discovery/resolver.ts +325 -0
  331. package/src/client/errors.ts +217 -0
  332. package/src/client/events.ts +12 -0
  333. package/src/client/handshake/crypt-handshake.ts +157 -0
  334. package/src/client/handshake/crypt-init2.ts +48 -0
  335. package/src/client/handshake/index.ts +3 -0
  336. package/src/client/handshake/license.ts +208 -0
  337. package/src/client/handshake.ts +150 -0
  338. package/src/client/helpers.ts +10 -0
  339. package/src/client/hooks.ts +30 -0
  340. package/src/client/index.ts +93 -0
  341. package/src/client/reconnect.ts +43 -0
  342. package/src/client/throttle.ts +92 -0
  343. package/src/client/trace.ts +237 -0
  344. package/src/client/transfer.ts +436 -0
  345. package/src/client/transport/generation-window.ts +108 -0
  346. package/src/client/transport/handler.ts +926 -0
  347. package/src/client/transport/index.ts +14 -0
  348. package/src/client/transport/packet.ts +69 -0
  349. package/src/client/transport/quicklz.ts +209 -0
  350. package/src/client/types.ts +268 -0
  351. package/src/core/api/channels.ts +396 -0
  352. package/src/core/api/clients.ts +654 -0
  353. package/src/core/api/command.ts +563 -0
  354. package/src/core/api/files.ts +405 -0
  355. package/src/core/api/groups.ts +622 -0
  356. package/src/core/api/moderation.ts +525 -0
  357. package/src/core/api/parse.ts +52 -0
  358. package/src/core/api/server.ts +709 -0
  359. package/src/core/command/command.ts +125 -0
  360. package/src/core/command/ids.ts +149 -0
  361. package/src/core/command/parser.ts +68 -0
  362. package/src/core/command/request.ts +119 -0
  363. package/src/core/command/responses.ts +114 -0
  364. package/src/core/command-name.ts +334 -0
  365. package/src/core/enums.ts +167 -0
  366. package/src/core/errorcode.ts +412 -0
  367. package/src/core/errors.ts +258 -0
  368. package/src/core/events.ts +219 -0
  369. package/src/core/helpers.ts +161 -0
  370. package/src/core/index.ts +126 -0
  371. package/src/core/internal.ts +23 -0
  372. package/src/core/middleware.ts +24 -0
  373. package/src/core/notification-name.ts +155 -0
  374. package/src/core/notifications.ts +157 -0
  375. package/src/core/permission-name.ts +538 -0
  376. package/src/core/permission.ts +34 -0
  377. package/src/core/property-name.ts +411 -0
  378. package/src/core/reason.ts +38 -0
  379. package/src/core/reconnect.ts +98 -0
  380. package/src/core/types.ts +138 -0
  381. package/src/query/client.ts +1132 -0
  382. package/src/query/errors.ts +108 -0
  383. package/src/query/index.ts +65 -0
  384. package/src/query/knownhosts.ts +257 -0
  385. package/src/query/lines.ts +28 -0
  386. package/src/query/options.ts +388 -0
  387. package/src/query/reconnect.ts +84 -0
  388. package/src/query/ssh.ts +614 -0
  389. package/src/query/stream.ts +91 -0
  390. package/src/query/tcp.ts +85 -0
  391. package/src/query/transport.ts +35 -0
  392. package/src/query/web.ts +195 -0
  393. package/src/versions/index.ts +10774 -0
@@ -0,0 +1,563 @@
1
+ /**
2
+ * Typed command layer.
3
+ *
4
+ * Every command both the voice protocol and ServerQuery accept is one function
5
+ * here, running on any CommandExecutor. Functions that only send a command
6
+ * return `Promise<void>`; functions that read return a parsed type. Every one
7
+ * takes a last, optional {@link CommandOptions} — a signal to abandon it and a
8
+ * timeoutMs — which is passed to the executor as it is.
9
+ *
10
+ * Nothing here re-encodes ids or field names by hand at the call site: the
11
+ * branded id types, the field tables below and the request encoder
12
+ * turn parameters into wire form in one place.
13
+ */
14
+
15
+ import { ErrorCode } from "../errorcode.js";
16
+ import { NoDataError, ServerError } from "../errors.js";
17
+ import type { CommandExecutor, CommandOptions, ParamValue } from "../command/request.js";
18
+ import { asChannelID, asChannelUID, asPermissionID, parseIconID } from "../command/ids.js";
19
+ import type { ChannelID, ChannelUID, IconID } from "../command/ids.js";
20
+ import { permissionByID, permissionByName } from "../permission.js";
21
+ import type { Permission } from "../permission.js";
22
+
23
+ /** Parameters in the order they are sent. */
24
+ export type Params = ReadonlyArray<readonly [string, ParamValue]>;
25
+
26
+ // ---- Sending ----------------------------------------------------------------
27
+
28
+ /**
29
+ * Send a command that produces no data and wait for the server's answer.
30
+ *
31
+ * `passwords` names the parameters that carry a channel password. Naming them
32
+ * here, rather than encoding them, leaves the encoding to the transport — the
33
+ * voice protocol hashes them, ServerQuery does not — and keeps every call site
34
+ * from having to remember, which is how a raw password once reached the server
35
+ * and failed 781.
36
+ */
37
+ async function exec(
38
+ client: CommandExecutor,
39
+ command: string,
40
+ params: Params = [],
41
+ options?: CommandOptions,
42
+ passwords: readonly string[] = [],
43
+ ): Promise<void> {
44
+ await client.send({ command, params, passwords }, commandOptions(options));
45
+ }
46
+
47
+ /**
48
+ * The signal and timeoutMs of a command's options, alone: a command's own
49
+ * options extend CommandOptions, and the executor, and any middleware on it,
50
+ * should see only what is theirs.
51
+ */
52
+ function commandOptions(options: CommandOptions | undefined): CommandOptions | undefined {
53
+ if (options === undefined) return undefined;
54
+ return { signal: options.signal, timeoutMs: options.timeoutMs };
55
+ }
56
+
57
+ /** Send a command and return its response rows. */
58
+ async function query(
59
+ client: CommandExecutor,
60
+ command: string,
61
+ params: Params = [],
62
+ options?: CommandOptions,
63
+ passwords: readonly string[] = [],
64
+ ): Promise<Array<Record<string, string>>> {
65
+ return client.send({ command, params, passwords }, commandOptions(options));
66
+ }
67
+
68
+ /** Whether an error is the server's "database empty result set" (1281). */
69
+ const isEmptyResult = (err: unknown): boolean =>
70
+ err instanceof ServerError && err.code === ErrorCode.DatabaseEmptyResult;
71
+
72
+ /**
73
+ * Send a command that lists or finds something and return its rows.
74
+ *
75
+ * The server reports an empty list as error 1281, "database empty result set",
76
+ * rather than as no rows — `banlist` on a server with no bans, `messagelist`
77
+ * with no messages — which made every list function throw where it should
78
+ * have answered `[]`. A search may report finding nothing with an error of its
79
+ * own, `how.noMatch`: `clientfind` answers 512 and `channelfind` 768, which for a
80
+ * command naming no client or channel can mean nothing else. `how.passwords`
81
+ * is as for {@link exec}.
82
+ */
83
+ async function list(
84
+ client: CommandExecutor,
85
+ command: string,
86
+ params: Params = [],
87
+ options?: CommandOptions,
88
+ how: { readonly noMatch?: number; readonly passwords?: readonly string[] } = {},
89
+ ): Promise<Array<Record<string, string>>> {
90
+ const { noMatch, passwords } = how;
91
+ try {
92
+ return await query(client, command, params, options, passwords);
93
+ } catch (err) {
94
+ if (isEmptyResult(err)) return [];
95
+ if (noMatch !== undefined && err instanceof ServerError && err.code === noMatch) return [];
96
+ throw err;
97
+ }
98
+ }
99
+
100
+ /** Default page size for a paged walk. */
101
+ const DEFAULT_PAGE_SIZE = 100;
102
+
103
+ /** How a paged command is walked, as by {@link iterateClientDatabase}. */
104
+ export interface PagingOptions extends CommandOptions {
105
+ /**
106
+ * How many rows to ask for per page; 100 by default. The server may cap a
107
+ * page below this, which the walk allows for.
108
+ */
109
+ readonly pageSize?: number | undefined;
110
+ }
111
+
112
+ /**
113
+ * Walk a command the server pages with `start` and `duration`, a page at a
114
+ * time, as the caller asks for more.
115
+ *
116
+ * The walk ends when the server answers a `start` past the last row with error
117
+ * 1281, "database empty result set", on an empty page, or on a page shorter
118
+ * than an earlier one. A short *first* page does not end it: the server may cap
119
+ * a page below what was asked, so the walk asks once more to find out. It also
120
+ * ends when a page starts at or before the last row already seen
121
+ * (`advanced` says), which is a server ignoring `start` — without that check
122
+ * such a server would be asked for its first page forever.
123
+ *
124
+ * Any other error is thrown from the iteration. Rows added or removed during
125
+ * the walk can be skipped or seen twice, since each page is a separate query.
126
+ *
127
+ * `options` reaches every page's command.
128
+ */
129
+ async function* pages<T>(
130
+ client: CommandExecutor,
131
+ command: string,
132
+ parse: (row: Record<string, string>) => T,
133
+ advanced: (previous: T, next: T) => boolean,
134
+ options: PagingOptions = {},
135
+ ): AsyncGenerator<T, void, undefined> {
136
+ const { pageSize = DEFAULT_PAGE_SIZE, ...rest } = options;
137
+ if (!Number.isInteger(pageSize) || pageSize < 1) {
138
+ throw new RangeError(`a page size is a positive integer, not ${pageSize}`);
139
+ }
140
+
141
+ let start = 0;
142
+ let fullPage = 0;
143
+ let last: T | undefined;
144
+ for (;;) {
145
+ let rows: Array<Record<string, string>>;
146
+ try {
147
+ rows = await query(
148
+ client,
149
+ command,
150
+ [
151
+ ["start", start],
152
+ ["duration", pageSize],
153
+ ],
154
+ rest,
155
+ );
156
+ } catch (err) {
157
+ if (isEmptyResult(err)) return;
158
+ throw err;
159
+ }
160
+ if (rows.length === 0) return;
161
+
162
+ const parsed = rows.map(parse);
163
+ if (last !== undefined && !advanced(last, parsed[0] as T)) return;
164
+ for (const item of parsed) yield item;
165
+ last = parsed[parsed.length - 1];
166
+
167
+ if (rows.length < fullPage) return;
168
+ fullPage = Math.max(fullPage, rows.length);
169
+ start += rows.length;
170
+ }
171
+ }
172
+
173
+ /**
174
+ * The row a command is defined to answer with, which must hold `key` when one
175
+ * is named. A server that acknowledged the command without it has not
176
+ * answered, which is a NoDataError rather than a row of empty fields.
177
+ */
178
+ function firstRow(
179
+ rows: ReadonlyArray<Record<string, string>>,
180
+ command: string,
181
+ key?: string,
182
+ ): Record<string, string> {
183
+ const row = rows[0];
184
+ if (row === undefined || (key !== undefined && (row[key] ?? "") === "")) {
185
+ throw new NoDataError(command);
186
+ }
187
+ return row;
188
+ }
189
+
190
+ export { exec, query, list, pages, commandOptions, firstRow };
191
+ export type { ParamValue };
192
+
193
+ // ---- Field tables -----------------------------------------------------------
194
+
195
+ /**
196
+ * How one option field goes on the wire: its parameter name, or its name and a
197
+ * function from the field's value to the parameter's. An encoder may return
198
+ * undefined to omit the parameter.
199
+ */
200
+ export type WireField<V> = string | readonly [name: string, encode: (value: V) => ParamValue];
201
+
202
+ /**
203
+ * A wire name for every field of an options type.
204
+ *
205
+ * Every key is required, so a field added to the options interface without a
206
+ * wire name is a compile error rather than a field silently never sent; the
207
+ * signal and timeoutMs every command's options carry are not parameters. A
208
+ * field whose type is not already a parameter value — a duration in
209
+ * milliseconds sent in seconds, say — must have an encoder.
210
+ */
211
+ export type WireNames<T> = {
212
+ readonly [K in Exclude<keyof T, keyof CommandOptions>]-?: Exclude<
213
+ T[K],
214
+ undefined
215
+ > extends ParamValue
216
+ ? WireField<Exclude<T[K], undefined>>
217
+ : readonly [name: string, encode: (value: Exclude<T[K], undefined>) => ParamValue];
218
+ };
219
+
220
+ /**
221
+ * Encode an options object through its field table.
222
+ *
223
+ * The table, not the object, decides what is sent: a key missing from the
224
+ * table — a typo, or anything else a JavaScript caller put there — is ignored
225
+ * rather than forwarded onto the wire. An unset field (undefined, or null from
226
+ * JavaScript) is omitted; any other value is sent, so `""` is sent as an empty
227
+ * parameter, which is how a setting such as a channel password is cleared.
228
+ */
229
+ export function encodeFields<T extends object>(fields: T, names: WireNames<T>): Params {
230
+ const out: Array<readonly [string, ParamValue]> = [];
231
+ for (const key of Object.keys(names) as Array<keyof T & string>) {
232
+ const value = fields[key] as unknown;
233
+ if (value === undefined || value === null) continue;
234
+ const field = (names as Record<string, WireField<unknown>>)[key]!;
235
+ if (typeof field === "string") out.push([field, value as ParamValue]);
236
+ else out.push([field[0], field[1](value)]);
237
+ }
238
+ return out;
239
+ }
240
+
241
+ /** An encoder that omits an empty string instead of sending it. */
242
+ export const nonEmpty = (value: string): string | undefined => (value === "" ? undefined : value);
243
+
244
+ /**
245
+ * An encoder that omits an id of 0 instead of sending it: where a command
246
+ * takes an id as a filter or a target, 0 names no channel, client or group,
247
+ * so it can only mean "any" or "none".
248
+ */
249
+ export const nonZero = (value: bigint): bigint | undefined => (value === 0n ? undefined : value);
250
+
251
+ // ---- Units ------------------------------------------------------------------
252
+
253
+ /** One second, in milliseconds: the unit most durations travel in. */
254
+ export const SECOND = 1000;
255
+
256
+ /** One millisecond: the unit `client_idle_time` and its like travel in. */
257
+ export const MILLISECOND = 1;
258
+
259
+ /**
260
+ * A duration in milliseconds, as the count of `unitMs` the wire carries.
261
+ *
262
+ * Rounds to the nearest unit, except that a positive duration never becomes 0:
263
+ * several fields read 0 as "forever" (a ban) or "never" (a temporary
264
+ * password), so half a second sent as 0 seconds would turn a short ban into a
265
+ * permanent one. A negative or non-finite duration is refused.
266
+ */
267
+ export function toWireDuration(ms: number, unitMs: number): number {
268
+ if (!Number.isFinite(ms) || ms < 0) {
269
+ throw new RangeError(`a duration is a non-negative number of milliseconds, not ${ms}`);
270
+ }
271
+ const units = Math.round(ms / unitMs);
272
+ return units === 0 && ms > 0 ? 1 : units;
273
+ }
274
+
275
+ /** An encoder for a millisecond field the wire carries in seconds. */
276
+ export const inSeconds = (ms: number): number => toWireDuration(ms, SECOND);
277
+
278
+ // ---- Reading rows -----------------------------------------------------------
279
+
280
+ /**
281
+ * Read a string field, tolerating absence.
282
+ *
283
+ * The parser has already unescaped it; unescaping again would turn a nickname
284
+ * that really contains `\s` into one with a space.
285
+ */
286
+ const str = (row: Record<string, string>, key: string): string => row[key] ?? "";
287
+
288
+ /**
289
+ * Read an unsigned 64-bit field, tolerating absence. Garbage, and a value
290
+ * outside 0 to 2^64-1, read as 0: every field read this way is an id or a
291
+ * size, and reading one must not throw where the id constructors would.
292
+ */
293
+ const big = (row: Record<string, string>, key: string): bigint => {
294
+ const raw = row[key];
295
+ if (raw === undefined || !/^\d+$/.test(raw)) return 0n;
296
+ const value = BigInt(raw);
297
+ return value > MAX_UINT64 ? 0n : value;
298
+ };
299
+
300
+ const MAX_UINT64 = (1n << 64n) - 1n;
301
+
302
+ /**
303
+ * Read an id no wider than `max`: an integer from 0 to `max`, or 0 for
304
+ * anything else, so the id constructors never see a value they refuse.
305
+ */
306
+ const uint = (row: Record<string, string>, key: string, max: number): number => {
307
+ const value = num(row, key);
308
+ return Number.isInteger(value) && value >= 0 && value <= max ? value : 0;
309
+ };
310
+
311
+ /** The widths {@link uint} reads ids at. */
312
+ const U16 = 0xffff;
313
+ const U32 = 0xffff_ffff;
314
+
315
+ /** Read a number field, tolerating absence. Fractions are kept. */
316
+ const num = (row: Record<string, string>, key: string): number => {
317
+ const raw = row[key];
318
+ if (raw === undefined || raw === "") return 0;
319
+ const parsed = Number(raw);
320
+ return Number.isNaN(parsed) ? 0 : parsed;
321
+ };
322
+
323
+ /** Read a number field that may be absent: undefined when the key is missing, else as {@link num}. */
324
+ const optNum = (row: Record<string, string>, key: string): number | undefined =>
325
+ row[key] === undefined ? undefined : num(row, key);
326
+
327
+ /** Read a boolean field: the protocol writes 0/1. */
328
+ const bool = (row: Record<string, string>, key: string): boolean => num(row, key) !== 0;
329
+
330
+ /** Read an icon id, signed or unsigned, as its unsigned value; 0 is no icon. */
331
+ const icon = (row: Record<string, string>, key: string): IconID | undefined =>
332
+ parseIconID(row[key]);
333
+
334
+ // Timestamps at or above these are not seconds. TS6 sends some in finer units —
335
+ // ftgetfilelist's datetime in milliseconds, ftgetfileinfo's in nanoseconds, for
336
+ // the same file — while TS3 sends seconds throughout. 1e11 seconds is the year
337
+ // 5138, and 1e11 milliseconds early 1973, so the magnitude settles the unit for
338
+ // any date this protocol will carry.
339
+ const MAX_UNIX_SECONDS = 10n ** 11n;
340
+ const MAX_UNIX_MILLIS = 10n ** 14n;
341
+ const MAX_UNIX_MICROS = 10n ** 17n;
342
+
343
+ /**
344
+ * Read a unix timestamp as a Date.
345
+ *
346
+ * 0, absence and garbage are `undefined`, not 1970: the server sends 0 for
347
+ * "never" (a client that never connected, a key never used).
348
+ */
349
+ const date = (row: Record<string, string>, key: string): Date | undefined => {
350
+ const raw = row[key];
351
+ if (raw === undefined || !/^-?\d+$/.test(raw)) return undefined;
352
+ const value = BigInt(raw);
353
+ if (value <= 0n) return undefined;
354
+ if (value < MAX_UNIX_SECONDS) return new Date(Number(value) * 1000);
355
+ if (value < MAX_UNIX_MILLIS) return new Date(Number(value));
356
+ if (value < MAX_UNIX_MICROS) return new Date(Number(value / 1000n));
357
+ return new Date(Number(value / 1_000_000n));
358
+ };
359
+
360
+ /** Read a duration sent as a count of `unitMs`, in milliseconds. */
361
+ const durationMs = (row: Record<string, string>, key: string, unitMs: number): number =>
362
+ num(row, key) * unitMs;
363
+
364
+ export { str, big, num, uint, U16, U32, bool, icon, date, durationMs };
365
+
366
+ // ---- Shared shapes ----------------------------------------------------------
367
+
368
+ /**
369
+ * The id of the group a create or copy just made.
370
+ *
371
+ * ServerQuery answers `servergroupadd` with the new id alone. The voice
372
+ * protocol answers with the whole group list instead (`notifyservergrouplist`),
373
+ * so taking the first row returned the first group on the server rather than
374
+ * the new one. The new group is the one with the requested name — the newest,
375
+ * should the name repeat across group types.
376
+ *
377
+ * The difference is a property of the answers, not a choice:
378
+ * `notifychannelcreated` names the client that invoked it, so `channelcreate`
379
+ * keeps only its own row, while the group commands answer with the whole list
380
+ * and name no invoker — `notifyservergrouplist` for `servergroupadd` and
381
+ * `servergroupcopy`, `notifychannelgrouplist` for `channelgroupadd` and
382
+ * `channelgroupcopy` — leaving nothing but the name to match on. A group of
383
+ * the same name created concurrently by another party is therefore
384
+ * indistinguishable from ours, channel groups as much as server groups, and
385
+ * the id returned may be theirs rather than the one we just made. That is a
386
+ * limit of what the notification reports, not of this function.
387
+ */
388
+ export function createdGroupID(
389
+ rows: ReadonlyArray<Record<string, string>>,
390
+ idKey: "sgid" | "cgid",
391
+ name: string,
392
+ command: string,
393
+ ): bigint {
394
+ const listed = rows.filter((row) => row["name"] !== undefined);
395
+ if (listed.length === 0) return big(firstRow(rows, command, idKey), idKey);
396
+
397
+ let id = 0n;
398
+ for (const row of listed) {
399
+ if (row["name"] === name && big(row, idKey) > id) id = big(row, idKey);
400
+ }
401
+ if (id === 0n) {
402
+ throw new NoDataError(command, `the server did not report the new group "${name}"`);
403
+ }
404
+ return id;
405
+ }
406
+
407
+ /**
408
+ * Encode a permission as `permid` or `permsid`, whichever names it.
409
+ *
410
+ * A JavaScript caller can pass an object naming neither, and a command sent
411
+ * with neither parameter fails on the server with a less helpful error, if it
412
+ * fails at all — so that is refused here.
413
+ */
414
+ export function permissionParam(permission: Permission): readonly [string, ParamValue] {
415
+ const value = permission as { readonly id?: unknown; readonly name?: unknown };
416
+ if (typeof value.name === "string" && value.name !== "") return ["permsid", value.name];
417
+ // No permission has id 0: a Permission naming it names none.
418
+ if (typeof value.id === "number" && Number.isInteger(value.id) && value.id > 0) {
419
+ return ["permid", value.id];
420
+ }
421
+ throw new TypeError("a permission is permissionByID(id), id above 0, or permissionByName(name)");
422
+ }
423
+
424
+ /** A channel as returned by `channellist` or `channelinfo`. */
425
+ export interface Channel {
426
+ /** The channel's id. */
427
+ readonly id: ChannelID;
428
+ /**
429
+ * The channel this one sits under, or 0 at the top level.
430
+ *
431
+ * `channellist` names this `cpid` and `channelinfo` names it `pid` — the same
432
+ * value under two keys, which is why both are read here.
433
+ */
434
+ readonly parentID: ChannelID;
435
+ /** The channel's name. */
436
+ readonly name: string;
437
+ /** The topic shown under the name (`channel_topic`). */
438
+ readonly topic: string;
439
+ /**
440
+ * The description (`channel_description`). `channellist` does not carry it,
441
+ * so `listChannels` leaves it empty; `getChannelInfo` or the voice client's
442
+ * `getChannelDescription` has it.
443
+ */
444
+ readonly description: string;
445
+ /**
446
+ * The channel this one is sorted after among its siblings, or 0 for the
447
+ * first: `channel_order` is a channel id, not a position.
448
+ */
449
+ readonly order: ChannelID;
450
+ /** Whether joining needs a password. */
451
+ readonly hasPassword: boolean;
452
+ /** Whether new clients land here. */
453
+ readonly isDefault: boolean;
454
+ /** Whether the channel survives a server restart (`channel_flag_permanent`). */
455
+ readonly isPermanent: boolean;
456
+ /**
457
+ * Whether the channel is kept until the last client leaves it, but not
458
+ * across a restart (`channel_flag_semi_permanent`).
459
+ */
460
+ readonly isSemiPermanent: boolean;
461
+ /** The voice codec, one of {@link Codec}. */
462
+ readonly codec: number;
463
+ /** The codec's quality, 0 to 10. */
464
+ readonly codecQuality: number;
465
+ /** How many clients may be in the channel (`channel_maxclients`); -1 for no limit. */
466
+ readonly maxClients: number;
467
+ /**
468
+ * How many clients may be in the channel and its subchannels together
469
+ * (`channel_maxfamilyclients`); -1 for no limit.
470
+ */
471
+ readonly maxFamilyClients: number;
472
+ /**
473
+ * How many clients are in the channel now (`total_clients`).
474
+ * `listChannels` reports it; `getChannelInfo` leaves it undefined, because
475
+ * `channelinfo` does not carry it.
476
+ */
477
+ readonly totalClients: number | undefined;
478
+ /**
479
+ * How many clients are in the channel and its subchannels now
480
+ * (`total_clients_family`). `listChannels` reports it; `getChannelInfo`
481
+ * leaves it undefined, because `channelinfo` does not carry it.
482
+ */
483
+ readonly totalFamilyClients: number | undefined;
484
+ /**
485
+ * The subscribe power a client needs to subscribe to the channel
486
+ * (`channel_needed_subscribe_power`). `listChannels` reports it;
487
+ * `getChannelInfo` leaves it undefined, because `channelinfo` does not
488
+ * carry it.
489
+ */
490
+ readonly neededSubscribePower: number | undefined;
491
+ /** The talk power a client needs to speak here. */
492
+ readonly neededTalkPower: number;
493
+ /** The channel's icon, or undefined for none. */
494
+ readonly iconID: IconID | undefined;
495
+ /**
496
+ * The unique identifier the server gives the channel
497
+ * (`channel_unique_identifier`): TS6 sends one, TS3 none, which reads as "".
498
+ * `channellist` does not carry it, so `listChannels` leaves it empty.
499
+ */
500
+ readonly uid: ChannelUID;
501
+ }
502
+
503
+ function parseChannel(row: Record<string, string>): Channel {
504
+ return {
505
+ id: asChannelID(big(row, "cid")),
506
+ parentID: asChannelID(big(row, "pid") || big(row, "cpid")),
507
+ name: str(row, "channel_name"),
508
+ topic: str(row, "channel_topic"),
509
+ description: str(row, "channel_description"),
510
+ order: asChannelID(big(row, "channel_order")),
511
+ hasPassword: bool(row, "channel_flag_password"),
512
+ isDefault: bool(row, "channel_flag_default"),
513
+ isPermanent: bool(row, "channel_flag_permanent"),
514
+ isSemiPermanent: bool(row, "channel_flag_semi_permanent"),
515
+ codec: num(row, "channel_codec"),
516
+ codecQuality: num(row, "channel_codec_quality"),
517
+ maxClients: num(row, "channel_maxclients"),
518
+ maxFamilyClients: num(row, "channel_maxfamilyclients"),
519
+ totalClients: optNum(row, "total_clients"),
520
+ totalFamilyClients: optNum(row, "total_clients_family"),
521
+ neededSubscribePower: optNum(row, "channel_needed_subscribe_power"),
522
+ neededTalkPower: num(row, "channel_needed_talk_power"),
523
+ iconID: icon(row, "channel_icon_id"),
524
+ uid: asChannelUID(str(row, "channel_unique_identifier")),
525
+ };
526
+ }
527
+
528
+ export { parseChannel };
529
+
530
+ /** One entry of a permission list. */
531
+ export interface PermissionEntry {
532
+ /**
533
+ * The permission the entry sets, named the way the server sent it — by id,
534
+ * unless a row carries the name. It can be passed straight back to the
535
+ * matching delete function.
536
+ */
537
+ readonly permission: Permission;
538
+ /** The permission's value. */
539
+ readonly value: number;
540
+ /** Whether the value is negated, taking the lowest value instead of the highest. */
541
+ readonly negated: boolean;
542
+ /** Whether the permission skips channel and client overrides. */
543
+ readonly skip: boolean;
544
+ }
545
+
546
+ /**
547
+ * Parse one permission row.
548
+ *
549
+ * A permission is addressable by id or by name, and the server answers in
550
+ * whichever form the request used, so a row carries one or the other.
551
+ */
552
+ export function parsePermission(row: Record<string, string>): PermissionEntry {
553
+ const name = str(row, "permsid");
554
+ return {
555
+ permission:
556
+ name === ""
557
+ ? permissionByID(asPermissionID(uint(row, "permid", U32)))
558
+ : permissionByName(name),
559
+ value: num(row, "permvalue"),
560
+ negated: bool(row, "permnegated"),
561
+ skip: bool(row, "permskip"),
562
+ };
563
+ }