@kyyinfinite/lumina 1.0.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/LICENSE +21 -0
- package/README.md +629 -0
- package/examples/ai-rich.js +84 -0
- package/examples/button.js +57 -0
- package/examples/carousel.js +51 -0
- package/examples/interactive.js +102 -0
- package/examples/media.js +66 -0
- package/examples/simple-bot.js +56 -0
- package/package.json +86 -0
- package/src/builders/ai-rich.js +644 -0
- package/src/builders/base.js +109 -0
- package/src/builders/button-v2.js +159 -0
- package/src/builders/button.js +398 -0
- package/src/builders/card.js +168 -0
- package/src/builders/carousel.js +122 -0
- package/src/builders/index.d.ts +1 -0
- package/src/builders/index.js +13 -0
- package/src/client/bot.js +192 -0
- package/src/client/connection.js +180 -0
- package/src/errors.js +88 -0
- package/src/index.d.ts +458 -0
- package/src/index.js +152 -0
- package/src/media/fetch.js +67 -0
- package/src/media/image.js +86 -0
- package/src/media/index.d.ts +1 -0
- package/src/media/index.js +12 -0
- package/src/media/resolver.js +115 -0
- package/src/media/uploader.js +65 -0
- package/src/media/video.js +195 -0
- package/src/parsers/code-tokenizer-keywords.js +128 -0
- package/src/parsers/code-tokenizer.js +191 -0
- package/src/parsers/index.d.ts +1 -0
- package/src/parsers/index.js +11 -0
- package/src/parsers/inline-entity.js +231 -0
- package/src/parsers/table-metadata.js +69 -0
- package/src/proto/enums.js +170 -0
- package/src/proto/index.d.ts +1 -0
- package/src/proto/index.js +13 -0
- package/src/proto/layouts.js +89 -0
- package/src/proto/primitives.js +181 -0
- package/src/proto/relay-nodes.js +55 -0
- package/src/proto/rich-response.js +144 -0
- package/src/proto/updater.js +318 -0
- package/src/services/index.d.ts +1 -0
- package/src/services/index.js +10 -0
- package/src/services/media-service.js +184 -0
- package/src/services/message-service.js +288 -0
- package/src/services/proto-service.js +90 -0
- package/src/utils/id.js +42 -0
- package/src/utils/logger.js +65 -0
- package/src/utils/mime.js +104 -0
- package/src/utils/promise.js +52 -0
- package/src/utils/validator.js +129 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file builders/card.js
|
|
3
|
+
* @module lumina/builders/card
|
|
4
|
+
*
|
|
5
|
+
* CardBuilder — composes a single carousel card. The legacy `Carousel` class
|
|
6
|
+
* required the user to construct card objects manually (including calling
|
|
7
|
+
* `prepareWAMessageMedia` themselves), which leaked the proto layer.
|
|
8
|
+
*
|
|
9
|
+
* CardBuilder is structurally identical to {@link ButtonBuilder} minus the
|
|
10
|
+
* `send()` method — cards are not standalone messages, they're embedded
|
|
11
|
+
* inside a carousel.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { applyContentFields } from './base.js'
|
|
15
|
+
import { coerceMediaSource, ensurePlainObject } from '../utils/validator.js'
|
|
16
|
+
import { uuid } from '../utils/id.js'
|
|
17
|
+
|
|
18
|
+
/** @typedef {import('../client/connection.js').Connection} Connection */
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Registry of simple button types (same as ButtonBuilder, kept local so the
|
|
22
|
+
* card module stays self-contained for tree-shaking).
|
|
23
|
+
* @type {Record<string, string>}
|
|
24
|
+
*/
|
|
25
|
+
const SIMPLE_BUTTON_TYPES = {
|
|
26
|
+
reply: 'quick_reply',
|
|
27
|
+
call: 'cta_call',
|
|
28
|
+
reminder: 'cta_reminder',
|
|
29
|
+
cancelReminder: 'cta_cancel_reminder',
|
|
30
|
+
address: 'address_message',
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class CardBuilder {
|
|
34
|
+
/** @param {Connection} conn */
|
|
35
|
+
constructor(conn) {
|
|
36
|
+
applyContentFields(this)
|
|
37
|
+
this.#conn = conn
|
|
38
|
+
/** @type {Array<{ name: string, buttonParamsJson: string }>} */
|
|
39
|
+
this._buttons = []
|
|
40
|
+
/** @type {object | null} */
|
|
41
|
+
this._data = null
|
|
42
|
+
/** @type {object} */
|
|
43
|
+
this._params = {}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** @type {Connection} */ #conn
|
|
47
|
+
|
|
48
|
+
// ─── Media ──────────────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @param {'image'|'video'|'document'} type
|
|
52
|
+
* @param {string | Buffer | object} source
|
|
53
|
+
* @param {object} [opts]
|
|
54
|
+
*/
|
|
55
|
+
media(type, source, opts = {}) {
|
|
56
|
+
if (!['image', 'video', 'document'].includes(type)) {
|
|
57
|
+
throw new TypeError(`media type must be image | video | document, got: ${type}`)
|
|
58
|
+
}
|
|
59
|
+
if (!source) throw new TypeError('media source is required')
|
|
60
|
+
this._data = { [type]: coerceMediaSource(source).raw, ...opts }
|
|
61
|
+
return this
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** @param {string | Buffer} source */
|
|
65
|
+
image(source, opts) {
|
|
66
|
+
return this.media('image', source, opts)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** @param {string | Buffer} source */
|
|
70
|
+
video(source, opts) {
|
|
71
|
+
return this.media('video', source, opts)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** @param {string | Buffer} source */
|
|
75
|
+
document(source, opts) {
|
|
76
|
+
return this.media('document', source, opts)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ─── Buttons ────────────────────────────────────────────────────────
|
|
80
|
+
|
|
81
|
+
params(obj) {
|
|
82
|
+
ensurePlainObject(obj, 'params')
|
|
83
|
+
this._params = obj
|
|
84
|
+
return this
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
button(name, params, extra = {}) {
|
|
88
|
+
this._buttons.push({
|
|
89
|
+
name,
|
|
90
|
+
buttonParamsJson: typeof params === 'string' ? params : JSON.stringify(params),
|
|
91
|
+
...extra,
|
|
92
|
+
})
|
|
93
|
+
return this
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
reply(displayText, id = uuid(), opts = {}) {
|
|
97
|
+
this._buttons.push({
|
|
98
|
+
name: SIMPLE_BUTTON_TYPES.reply,
|
|
99
|
+
buttonParamsJson: JSON.stringify({ display_text: displayText, id, ...opts }),
|
|
100
|
+
})
|
|
101
|
+
return this
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
url(displayText, url, opts = {}) {
|
|
105
|
+
this._buttons.push({
|
|
106
|
+
name: 'cta_url',
|
|
107
|
+
buttonParamsJson: JSON.stringify({
|
|
108
|
+
display_text: displayText,
|
|
109
|
+
url,
|
|
110
|
+
webview_interaction: opts.webview_interaction ?? false,
|
|
111
|
+
...opts,
|
|
112
|
+
}),
|
|
113
|
+
})
|
|
114
|
+
return this
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
copy(displayText, copyCode, opts = {}) {
|
|
118
|
+
this._buttons.push({
|
|
119
|
+
name: 'cta_copy',
|
|
120
|
+
buttonParamsJson: JSON.stringify({
|
|
121
|
+
display_text: displayText,
|
|
122
|
+
copy_code: copyCode,
|
|
123
|
+
...opts,
|
|
124
|
+
}),
|
|
125
|
+
})
|
|
126
|
+
return this
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ─── Lifecycle ──────────────────────────────────────────────────────
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Build the proto-ready card object.
|
|
133
|
+
*
|
|
134
|
+
* @returns {Promise<object>}
|
|
135
|
+
*/
|
|
136
|
+
async build() {
|
|
137
|
+
let mediaPayload = {}
|
|
138
|
+
if (this._data) {
|
|
139
|
+
try {
|
|
140
|
+
// Layer 4 must not touch Baileys directly — go through Connection.
|
|
141
|
+
mediaPayload = await this.#conn.uploadMedia(this._data)
|
|
142
|
+
} catch (err) {
|
|
143
|
+
if (err?.message?.includes('Invalid media type')) {
|
|
144
|
+
mediaPayload = this._data
|
|
145
|
+
} else {
|
|
146
|
+
throw err
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
body: { text: this._body },
|
|
153
|
+
footer: { text: this._footer },
|
|
154
|
+
header: {
|
|
155
|
+
title: this._title,
|
|
156
|
+
subtitle: this._subtitle,
|
|
157
|
+
hasMediaAttachment: !!this._data,
|
|
158
|
+
...(this._data ? mediaPayload : {}),
|
|
159
|
+
},
|
|
160
|
+
nativeFlowMessage: {
|
|
161
|
+
messageParamsJson: JSON.stringify(this._params),
|
|
162
|
+
buttons: this._buttons,
|
|
163
|
+
},
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export default CardBuilder
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file builders/carousel.js
|
|
3
|
+
* @module lumina/builders/carousel
|
|
4
|
+
*
|
|
5
|
+
* CarouselBuilder — composes a `carouselMessage` containing one or more
|
|
6
|
+
* cards produced by {@link CardBuilder}.
|
|
7
|
+
*
|
|
8
|
+
* Bug fix vs legacy: the original `Carousel.send()` forgot to `await`
|
|
9
|
+
* `build()`. Lumina's version is consistently async and awaits throughout.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { applyContentFields } from './base.js'
|
|
13
|
+
import { createInteractiveNodes } from '../proto/relay-nodes.js'
|
|
14
|
+
import { CardBuilder } from './card.js'
|
|
15
|
+
|
|
16
|
+
/** @typedef {import('../client/connection.js').Connection} Connection */
|
|
17
|
+
/** @typedef {import('../services/proto-service.js').ProtoService} ProtoService */
|
|
18
|
+
/** @typedef {import('../services/media-service.js').MediaService} MediaService */
|
|
19
|
+
|
|
20
|
+
export class CarouselBuilder {
|
|
21
|
+
/**
|
|
22
|
+
* @param {Connection} conn
|
|
23
|
+
* @param {ProtoService} proto
|
|
24
|
+
* @param {MediaService} media
|
|
25
|
+
*/
|
|
26
|
+
constructor(conn, proto, media) {
|
|
27
|
+
applyContentFields(this, { body: '', footer: '' })
|
|
28
|
+
this.#conn = conn
|
|
29
|
+
this.#proto = proto
|
|
30
|
+
this.#media = media
|
|
31
|
+
/** @type {Array<object>} raw, already-built card objects */
|
|
32
|
+
this._cards = []
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** @type {Connection} */ #conn
|
|
36
|
+
/** @type {ProtoService} */ #proto
|
|
37
|
+
/** @type {MediaService} */ #media
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Create a fresh {@link CardBuilder} bound to this carousel's connection.
|
|
41
|
+
* User builds it inline, calls `.build()` on it, and passes the result
|
|
42
|
+
* back to {@link card}.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* const card = await carousel.newCard()
|
|
46
|
+
* .title('A').image('a.jpg').reply('Buy', 'buy_a').build()
|
|
47
|
+
* carousel.card(card)
|
|
48
|
+
*
|
|
49
|
+
* @returns {CardBuilder}
|
|
50
|
+
*/
|
|
51
|
+
newCard() {
|
|
52
|
+
return new CardBuilder(this.#conn)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Append one or more pre-built cards.
|
|
57
|
+
*
|
|
58
|
+
* @param {object | object[]} card
|
|
59
|
+
*/
|
|
60
|
+
card(card) {
|
|
61
|
+
const cards = Array.isArray(card) ? card : [card]
|
|
62
|
+
for (const [i, c] of cards.entries()) {
|
|
63
|
+
if (!c?.header?.hasMediaAttachment) {
|
|
64
|
+
throw new Error(`Card [${this._cards.length + i}] must include an image or video in header`)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
this._cards.push(...cards)
|
|
68
|
+
return this
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Variadic alias for {@link card}.
|
|
73
|
+
*
|
|
74
|
+
* @param {...object} cards
|
|
75
|
+
*/
|
|
76
|
+
cards(...cards) {
|
|
77
|
+
return this.card(cards)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Build the proto message object (without relaying).
|
|
82
|
+
*
|
|
83
|
+
* @param {string} jid
|
|
84
|
+
* @param {object} [opts]
|
|
85
|
+
* @returns {Promise<object>}
|
|
86
|
+
*/
|
|
87
|
+
async build(jid, opts = {}) {
|
|
88
|
+
return this.#conn.generateMessage(
|
|
89
|
+
jid,
|
|
90
|
+
{
|
|
91
|
+
...this._extraPayload,
|
|
92
|
+
interactiveMessage: {
|
|
93
|
+
header: { hasMediaAttachment: false },
|
|
94
|
+
body: { text: this._body },
|
|
95
|
+
footer: { text: this._footer },
|
|
96
|
+
contextInfo: this._contextInfo,
|
|
97
|
+
carouselMessage: { cards: this._cards },
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
opts,
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Build + relay.
|
|
106
|
+
*
|
|
107
|
+
* @param {string} jid
|
|
108
|
+
* @param {object} [opts]
|
|
109
|
+
* @returns {Promise<object>}
|
|
110
|
+
*/
|
|
111
|
+
async send(jid, opts = {}) {
|
|
112
|
+
const msg = await this.build(jid, opts)
|
|
113
|
+
await this.#conn.relayMessage(msg.key.remoteJid, msg.message, {
|
|
114
|
+
messageId: msg.key.id,
|
|
115
|
+
additionalNodes: opts.additionalNodes ?? createInteractiveNodes(),
|
|
116
|
+
...opts,
|
|
117
|
+
})
|
|
118
|
+
return msg
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export default CarouselBuilder
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '../index.d.ts'
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file builders/index.js
|
|
3
|
+
* @module lumina/builders
|
|
4
|
+
*
|
|
5
|
+
* Barrel re-export for the builders layer.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export { ButtonBuilder } from './button.js'
|
|
9
|
+
export { ButtonV2Builder } from './button-v2.js'
|
|
10
|
+
export { CarouselBuilder } from './carousel.js'
|
|
11
|
+
export { CardBuilder } from './card.js'
|
|
12
|
+
export { AIRichBuilder } from './ai-rich.js'
|
|
13
|
+
export { applyContentFields, readContentFields } from './base.js'
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file client/bot.js
|
|
3
|
+
* @module lumina/client/bot
|
|
4
|
+
*
|
|
5
|
+
* Bot — primary entry point. Wraps a Baileys socket, exposes a fluent,
|
|
6
|
+
* verb-first API, and provides factory methods for every builder.
|
|
7
|
+
*
|
|
8
|
+
* const bot = new Bot(socket)
|
|
9
|
+
* await bot.text(jid, 'Halo')
|
|
10
|
+
* await bot.button().title('T').body('B').reply('OK').send(jid)
|
|
11
|
+
* await bot.ai().text('hi').code('js', 'console.log(1)').send(jid)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { Connection } from './connection.js'
|
|
15
|
+
import { MediaService } from '../services/media-service.js'
|
|
16
|
+
import { ProtoService } from '../services/proto-service.js'
|
|
17
|
+
import { MessageService } from '../services/message-service.js'
|
|
18
|
+
|
|
19
|
+
import { ButtonBuilder } from '../builders/button.js'
|
|
20
|
+
import { ButtonV2Builder } from '../builders/button-v2.js'
|
|
21
|
+
import { CarouselBuilder } from '../builders/carousel.js'
|
|
22
|
+
import { AIRichBuilder } from '../builders/ai-rich.js'
|
|
23
|
+
|
|
24
|
+
import { extractInlineEntities, tokenizeCode, toTableMetadata } from '../parsers/index.js'
|
|
25
|
+
import { createLogger } from '../utils/logger.js'
|
|
26
|
+
|
|
27
|
+
/** @typedef {import('@whiskeysockets/baileys').WASocket} WASocket */
|
|
28
|
+
/** @typedef {import('../utils/logger.js').Logger} Logger */
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @typedef {object} BotOptions
|
|
32
|
+
* @property {string} [uploadJid] Pre-upload JID for media uploads.
|
|
33
|
+
* @property {Logger} [logger] Custom logger.
|
|
34
|
+
* @property {boolean} [mediaCache=true] Enable MediaService URL cache.
|
|
35
|
+
* @property {string} [uploadJidDefault] Alias for uploadJid.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
export class Bot {
|
|
39
|
+
/**
|
|
40
|
+
* @param {WASocket} socket
|
|
41
|
+
* @param {BotOptions} [opts]
|
|
42
|
+
*/
|
|
43
|
+
constructor(socket, opts = {}) {
|
|
44
|
+
this.logger = opts.logger ?? createLogger({ level: 'warn' })
|
|
45
|
+
|
|
46
|
+
this.connection = new Connection(socket, {
|
|
47
|
+
uploadJid: opts.uploadJid ?? opts.uploadJidDefault,
|
|
48
|
+
logger: this.logger,
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
this.media = new MediaService(this.connection, {
|
|
52
|
+
logger: this.logger,
|
|
53
|
+
uploadJid: opts.uploadJid,
|
|
54
|
+
cache: opts.mediaCache ?? true,
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
this.proto = new ProtoService(this.connection)
|
|
58
|
+
this.message = new MessageService(this.connection, this.proto, this.media)
|
|
59
|
+
|
|
60
|
+
// Expose util facade as a property — bot.util.extractInlineEntities(text)
|
|
61
|
+
this.util = {
|
|
62
|
+
extractInlineEntities,
|
|
63
|
+
tokenizeCode,
|
|
64
|
+
toTableMetadata,
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ─── Basic senders ─────────────────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
/** @param {string} jid @param {string} text @param {object} [opts] */
|
|
71
|
+
text(jid, text, opts) {
|
|
72
|
+
return this.message.text(jid, text, opts)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** @param {string} jid @param {string | Buffer} source @param {string} [caption] @param {object} [opts] */
|
|
76
|
+
image(jid, source, caption, opts) {
|
|
77
|
+
return this.message.image(jid, source, caption, opts)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** @param {string} jid @param {string | Buffer} source @param {string} [caption] @param {object} [opts] */
|
|
81
|
+
video(jid, source, caption, opts) {
|
|
82
|
+
return this.message.video(jid, source, caption, opts)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** @param {string} jid @param {string | Buffer} source @param {object} [opts] */
|
|
86
|
+
audio(jid, source, opts) {
|
|
87
|
+
return this.message.audio(jid, source, opts)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** @param {string} jid @param {string | Buffer} source @param {object} [opts] */
|
|
91
|
+
document(jid, source, opts) {
|
|
92
|
+
return this.message.document(jid, source, opts)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** @param {string} jid @param {string | Buffer} source @param {object} [opts] */
|
|
96
|
+
sticker(jid, source, opts) {
|
|
97
|
+
return this.message.sticker(jid, source, opts)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** @param {string} jid @param {Array} contacts @param {object} [opts] */
|
|
101
|
+
contact(jid, contacts, opts) {
|
|
102
|
+
return this.message.contact(jid, contacts, opts)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** @param {string} jid @param {number} lat @param {number} lng @param {object} [opts] */
|
|
106
|
+
location(jid, lat, lng, opts) {
|
|
107
|
+
return this.message.location(jid, lat, lng, opts)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** @param {string} jid @param {string} name @param {string[]} options @param {object} [opts] */
|
|
111
|
+
poll(jid, name, options, opts) {
|
|
112
|
+
return this.message.poll(jid, name, options, opts)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** @param {string} jid @param {string} text @param {object} quoted @param {object} [opts] */
|
|
116
|
+
reply(jid, text, quoted, opts) {
|
|
117
|
+
return this.message.reply(jid, text, quoted, opts)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** @param {string} jid @param {object} key @param {string} emoji @param {object} [opts] */
|
|
121
|
+
react(jid, key, emoji, opts) {
|
|
122
|
+
return this.message.react(jid, key, emoji, opts)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** @param {string} jid @param {object} key */
|
|
126
|
+
delete(jid, key) {
|
|
127
|
+
return this.message.delete(jid, key)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** @param {string} jid @param {object} message @param {object} [opts] */
|
|
131
|
+
forward(jid, message, opts) {
|
|
132
|
+
return this.message.forward(jid, message, opts)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** @param {string} jid @param {object} message @param {object} [opts] */
|
|
136
|
+
copy(jid, message, opts) {
|
|
137
|
+
return this.message.copy(jid, message, opts)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** @param {string} jid @param {object} key @param {string} newText @param {object} [opts] */
|
|
141
|
+
edit(jid, key, newText, opts) {
|
|
142
|
+
return this.message.edit(jid, key, newText, opts)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ─── Builder factories ─────────────────────────────────────────────
|
|
146
|
+
|
|
147
|
+
/** @returns {ButtonBuilder} */
|
|
148
|
+
button() {
|
|
149
|
+
return new ButtonBuilder(this.connection, this.proto, this.media)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** @returns {ButtonV2Builder} */
|
|
153
|
+
buttonV2() {
|
|
154
|
+
return new ButtonV2Builder(this.connection)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** @returns {CarouselBuilder} */
|
|
158
|
+
carousel() {
|
|
159
|
+
return new CarouselBuilder(this.connection, this.proto, this.media)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** @returns {AIRichBuilder} */
|
|
163
|
+
ai() {
|
|
164
|
+
return new AIRichBuilder(this.connection, this.proto, this.media)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ─── Event bus ─────────────────────────────────────────────────────
|
|
168
|
+
|
|
169
|
+
/** @param {string} event @param {(...a: any[]) => void} handler @returns {() => void} */
|
|
170
|
+
on(event, handler) {
|
|
171
|
+
return this.connection.on(event, handler)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** @param {string} event @param {(...a: any[]) => void} handler @returns {() => void} */
|
|
175
|
+
once(event, handler) {
|
|
176
|
+
return this.connection.once(event, handler)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** @param {string} event @param {(...a: any[]) => void} handler */
|
|
180
|
+
off(event, handler) {
|
|
181
|
+
this.connection.off(event, handler)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ─── Raw escape ────────────────────────────────────────────────────
|
|
185
|
+
|
|
186
|
+
/** @returns {WASocket} */
|
|
187
|
+
get raw() {
|
|
188
|
+
return this.connection.raw
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export default Bot
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file connection.js
|
|
3
|
+
* @module lumina/client/connection
|
|
4
|
+
*
|
|
5
|
+
* Connection — Baileys socket adapter. This is the ONLY module in the whole
|
|
6
|
+
* framework that knows about Baileys' raw API surface. Every other module
|
|
7
|
+
* talks to Baileys through one of the four ports Connection exposes:
|
|
8
|
+
*
|
|
9
|
+
* - MediaPort → uploadMedia
|
|
10
|
+
* - GeneratePort → generateMessage
|
|
11
|
+
* - RelayPort → relayMessage
|
|
12
|
+
* - EventBus → on / once / off
|
|
13
|
+
*
|
|
14
|
+
* If Baileys renames `relayMessage` to `sendRaw`, this is the only file
|
|
15
|
+
* that needs updating.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { ConnectionError } from '../errors.js'
|
|
19
|
+
|
|
20
|
+
/** @typedef {import('@whiskeysockets/baileys').WASocket} WASocket */
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @typedef {object} ConnectionOptions
|
|
24
|
+
* @property {string} [uploadJid] Default JID used for pre-uploading media.
|
|
25
|
+
* @property {import('../utils/logger.js').Logger} [logger]
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
export class Connection {
|
|
29
|
+
/** @param {WASocket} socket */
|
|
30
|
+
/** @param {ConnectionOptions} [opts] */
|
|
31
|
+
constructor(socket, opts = {}) {
|
|
32
|
+
if (!socket) {
|
|
33
|
+
throw new ConnectionError('socket is required', { code: 'LUMINA_CONNECTION_NO_SOCKET' })
|
|
34
|
+
}
|
|
35
|
+
this.#socket = socket
|
|
36
|
+
this.uploadJid = opts.uploadJid
|
|
37
|
+
this.logger = opts.logger
|
|
38
|
+
|
|
39
|
+
// Verify the bare-minimum API surface we depend on.
|
|
40
|
+
const required = ['relayMessage', 'ev']
|
|
41
|
+
for (const m of required) {
|
|
42
|
+
if (typeof socket[m] === 'undefined') {
|
|
43
|
+
throw new ConnectionError(`socket is missing required field: ${m}`, {
|
|
44
|
+
code: 'LUMINA_CONNECTION_INCOMPATIBLE',
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** @type {WASocket | null} */
|
|
51
|
+
#socket = null
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Upload media via Baileys' `prepareWAMessageMedia`. We lazy-import it so
|
|
55
|
+
* that the framework loads even if the user's Baileys version is older
|
|
56
|
+
* than the one we tested against.
|
|
57
|
+
*
|
|
58
|
+
* @param {object} media `{ image: Buffer | { url } }` etc.
|
|
59
|
+
* @param {object} [opts] `{ jid, ... }` forwarded to prepareWAMessageMedia.
|
|
60
|
+
* @returns {Promise<object>}
|
|
61
|
+
*/
|
|
62
|
+
async uploadMedia(media, opts = {}) {
|
|
63
|
+
const { prepareWAMessageMedia } = await import('@whiskeysockets/baileys')
|
|
64
|
+
const jid = opts.jid ?? this.uploadJid
|
|
65
|
+
if (!jid) {
|
|
66
|
+
throw new ConnectionError('uploadJid must be set (via constructor or per-call)', {
|
|
67
|
+
code: 'LUMINA_CONNECTION_NO_UPLOAD_JID',
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
return prepareWAMessageMedia(media, {
|
|
71
|
+
upload: this.#socket.waUploadToServer,
|
|
72
|
+
jid,
|
|
73
|
+
...opts,
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Generate a WA message object from a content descriptor.
|
|
79
|
+
*
|
|
80
|
+
* @param {string} jid
|
|
81
|
+
* @param {object} content
|
|
82
|
+
* @param {object} [opts]
|
|
83
|
+
* @returns {Promise<object>}
|
|
84
|
+
*/
|
|
85
|
+
async generateMessage(jid, content, opts = {}) {
|
|
86
|
+
const { generateWAMessageFromContent } = await import('@whiskeysockets/baileys')
|
|
87
|
+
return generateWAMessageFromContent(jid, content, opts)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Generate a poll message.
|
|
92
|
+
*
|
|
93
|
+
* @param {string} jid
|
|
94
|
+
* @param {object} opts { name, values, selectableCount, toJid, messageSecret }
|
|
95
|
+
* @returns {Promise<object>}
|
|
96
|
+
*/
|
|
97
|
+
async generatePoll(jid, opts) {
|
|
98
|
+
const { generatePollMessage } = await import('@whiskeysockets/baileys')
|
|
99
|
+
return generatePollMessage(jid, opts)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Generate a reaction message.
|
|
104
|
+
*
|
|
105
|
+
* @param {string} jid
|
|
106
|
+
* @param {object} opts { key, text }
|
|
107
|
+
* @returns {Promise<object>}
|
|
108
|
+
*/
|
|
109
|
+
async generateReaction(jid, opts) {
|
|
110
|
+
const { generateReactionMessage } = await import('@whiskeysockets/baileys')
|
|
111
|
+
return generateReactionMessage(jid, opts)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Relay a pre-built message via `relayMessage`. Returns the message ID.
|
|
116
|
+
*
|
|
117
|
+
* @param {string} jid
|
|
118
|
+
* @param {object} message
|
|
119
|
+
* @param {object} [opts]
|
|
120
|
+
* @returns {Promise<string>}
|
|
121
|
+
*/
|
|
122
|
+
async relayMessage(jid, message, opts = {}) {
|
|
123
|
+
const messageId = opts.messageId ?? this.#socket.generateMessageTag()
|
|
124
|
+
await this.#socket.relayMessage(jid, message, {
|
|
125
|
+
messageId,
|
|
126
|
+
...opts,
|
|
127
|
+
})
|
|
128
|
+
return messageId
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Subscribe to a Baileys event. Returns an unsubscriber.
|
|
133
|
+
*
|
|
134
|
+
* @param {string} event
|
|
135
|
+
* @param {(...args: any[]) => void} handler
|
|
136
|
+
* @returns {() => void}
|
|
137
|
+
*/
|
|
138
|
+
on(event, handler) {
|
|
139
|
+
this.#socket.ev.on(event, handler)
|
|
140
|
+
return () => this.#socket.ev.off(event, handler)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Subscribe once.
|
|
145
|
+
*
|
|
146
|
+
* @param {string} event
|
|
147
|
+
* @param {(...args: any[]) => void} handler
|
|
148
|
+
* @returns {() => void}
|
|
149
|
+
*/
|
|
150
|
+
once(event, handler) {
|
|
151
|
+
const off = this.on(event, (...args) => {
|
|
152
|
+
off()
|
|
153
|
+
handler(...args)
|
|
154
|
+
})
|
|
155
|
+
return off
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Unsubscribe.
|
|
160
|
+
*
|
|
161
|
+
* @param {string} event
|
|
162
|
+
* @param {(...args: any[]) => void} handler
|
|
163
|
+
*/
|
|
164
|
+
off(event, handler) {
|
|
165
|
+
this.#socket.ev.off(event, handler)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Raw Baileys socket — escape hatch for advanced users. Use sparingly;
|
|
170
|
+
* any code that touches `raw` bypasses Lumina's contract and may break
|
|
171
|
+
* on Baileys upgrades.
|
|
172
|
+
*
|
|
173
|
+
* @returns {WASocket}
|
|
174
|
+
*/
|
|
175
|
+
get raw() {
|
|
176
|
+
return this.#socket
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export default Connection
|