@kyyinfinite/lumina 1.0.2 → 1.0.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kyyinfinite/lumina",
3
- "version": "1.0.2",
3
+ "version": "1.0.5",
4
4
  "description": "Lumina — Modern WhatsApp framework built on top of Baileys. Hide the protocol complexity, expose a fluent API.",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -0,0 +1,140 @@
1
+ import crypto from 'node:crypto'
2
+ import { ConnectionError } from '../errors.js'
3
+
4
+ const KNOWN_PACKAGES = [
5
+ '@kyyinfinite/baileys',
6
+ '@whiskeysockets/baileys',
7
+ 'baileys',
8
+ '@adiwajshing/baileys',
9
+ '@brunocgc/baileys',
10
+ '@open-wa/baileys',
11
+ ]
12
+
13
+ const REQUIRED_FNS = [
14
+ 'prepareWAMessageMedia',
15
+ 'generateWAMessageFromContent',
16
+ 'generatePollMessage',
17
+ 'generateReactionMessage',
18
+ ]
19
+
20
+ function extractFns(mod) {
21
+ const missing = REQUIRED_FNS.filter((fn) => typeof mod[fn] !== 'function')
22
+ if (missing.length > 0) return null
23
+ return {
24
+ prepareWAMessageMedia: mod.prepareWAMessageMedia,
25
+ generateWAMessageFromContent: mod.generateWAMessageFromContent,
26
+ generatePollMessage: mod.generatePollMessage,
27
+ generateReactionMessage: mod.generateReactionMessage,
28
+ }
29
+ }
30
+
31
+ function makeFallbackFns(socket) {
32
+ const generateId = () => crypto.randomBytes(8).toString('hex').toUpperCase()
33
+
34
+ const generateWAMessageFromContent = (jid, content, opts = {}) => ({
35
+ key: {
36
+ remoteJid: jid,
37
+ fromMe: true,
38
+ id: opts.messageId ?? generateId(),
39
+ participant: opts.participant,
40
+ },
41
+ message: content,
42
+ messageTimestamp: Math.floor(Date.now() / 1000),
43
+ status: 1,
44
+ })
45
+
46
+ const generatePollMessage = async (jid, opts) => {
47
+ const msg = await socket.sendMessage(jid, {
48
+ poll: {
49
+ name: opts.name,
50
+ values: opts.values,
51
+ selectableCount: opts.selectableCount ?? 1,
52
+ },
53
+ })
54
+ return msg
55
+ }
56
+
57
+ const generateReactionMessage = (jid, opts) =>
58
+ generateWAMessageFromContent(jid, {
59
+ reactionMessage: { key: opts.key, text: opts.text },
60
+ })
61
+
62
+ const prepareWAMessageMedia = async (media, opts = {}) => {
63
+ if (!socket.waUploadToServer) {
64
+ throw new ConnectionError(
65
+ 'Cannot upload media: no baileys package found and socket.waUploadToServer is unavailable',
66
+ { code: 'LUMINA_ADAPTER_NO_UPLOAD' },
67
+ )
68
+ }
69
+ const [type, source] = Object.entries(media)[0]
70
+ const uploaded = await socket.waUploadToServer(
71
+ Buffer.isBuffer(source) ? source : source,
72
+ { mediaType: type, jid: opts.jid },
73
+ )
74
+ return { [type]: { url: uploaded.url, ...uploaded } }
75
+ }
76
+
77
+ return { generateWAMessageFromContent, generatePollMessage, generateReactionMessage, prepareWAMessageMedia }
78
+ }
79
+
80
+ export class BaileysAdapter {
81
+ #fns = null
82
+ #source = 'unknown'
83
+
84
+ static async resolve(socket, opts = {}) {
85
+ const adapter = new BaileysAdapter()
86
+
87
+ if (opts.baileys && typeof opts.baileys === 'object') {
88
+ const fns = extractFns(opts.baileys)
89
+ if (fns) {
90
+ adapter.#fns = fns
91
+ adapter.#source = 'injected-module'
92
+ return adapter
93
+ }
94
+ }
95
+
96
+ if (typeof opts.baileysPackage === 'string') {
97
+ try {
98
+ const mod = await import(opts.baileysPackage)
99
+ const fns = extractFns(mod)
100
+ if (fns) {
101
+ adapter.#fns = fns
102
+ adapter.#source = opts.baileysPackage
103
+ return adapter
104
+ }
105
+ } catch {
106
+ throw new ConnectionError(
107
+ `baileysPackage "${opts.baileysPackage}" could not be imported or is missing required functions`,
108
+ { code: 'LUMINA_ADAPTER_PACKAGE_NOT_FOUND' },
109
+ )
110
+ }
111
+ }
112
+
113
+ for (const pkg of KNOWN_PACKAGES) {
114
+ try {
115
+ const mod = await import(pkg)
116
+ const fns = extractFns(mod)
117
+ if (fns) {
118
+ adapter.#fns = fns
119
+ adapter.#source = pkg
120
+ return adapter
121
+ }
122
+ } catch {}
123
+ }
124
+
125
+ adapter.#fns = makeFallbackFns(socket)
126
+ adapter.#source = 'socket-fallback'
127
+ return adapter
128
+ }
129
+
130
+ get source() {
131
+ return this.#source
132
+ }
133
+
134
+ get prepareWAMessageMedia() { return this.#fns.prepareWAMessageMedia }
135
+ get generateWAMessageFromContent() { return this.#fns.generateWAMessageFromContent }
136
+ get generatePollMessage() { return this.#fns.generatePollMessage }
137
+ get generateReactionMessage() { return this.#fns.generateReactionMessage }
138
+ }
139
+
140
+ export default BaileysAdapter
@@ -1,11 +1,12 @@
1
1
  import { ConnectionError } from '../errors.js'
2
+ import { BaileysAdapter } from './baileys-adapter.js'
2
3
 
3
4
  export class Connection {
5
+ #socket = null
6
+ #adapter = null
7
+
4
8
  constructor(socket, opts = {}) {
5
9
  if (!socket) throw new ConnectionError('socket is required', { code: 'LUMINA_CONNECTION_NO_SOCKET' })
6
- this.#socket = socket
7
- this.uploadJid = opts.uploadJid
8
- this.logger = opts.logger
9
10
 
10
11
  const required = ['relayMessage', 'ev']
11
12
  for (const m of required) {
@@ -15,34 +16,46 @@ export class Connection {
15
16
  })
16
17
  }
17
18
  }
19
+
20
+ this.#socket = socket
21
+ this.uploadJid = opts.uploadJid
22
+ this.logger = opts.logger
23
+ this.#adapter = BaileysAdapter.resolve(socket, opts)
18
24
  }
19
25
 
20
- #socket = null
26
+ async #fn() {
27
+ if (this.#adapter instanceof Promise) this.#adapter = await this.#adapter
28
+ return this.#adapter
29
+ }
21
30
 
22
31
  async uploadMedia(media, opts = {}) {
23
- const { prepareWAMessageMedia } = await import('@whiskeysockets/baileys')
32
+ const adapter = await this.#fn()
24
33
  const jid = opts.jid ?? this.uploadJid
25
34
  if (!jid) {
26
35
  throw new ConnectionError('uploadJid must be set (via constructor or per-call)', {
27
36
  code: 'LUMINA_CONNECTION_NO_UPLOAD_JID',
28
37
  })
29
38
  }
30
- return prepareWAMessageMedia(media, { upload: this.#socket.waUploadToServer, jid, ...opts })
39
+ return adapter.prepareWAMessageMedia(media, {
40
+ upload: this.#socket.waUploadToServer,
41
+ jid,
42
+ ...opts,
43
+ })
31
44
  }
32
45
 
33
46
  async generateMessage(jid, content, opts = {}) {
34
- const { generateWAMessageFromContent } = await import('@whiskeysockets/baileys')
35
- return generateWAMessageFromContent(jid, content, opts)
47
+ const adapter = await this.#fn()
48
+ return adapter.generateWAMessageFromContent(jid, content, opts)
36
49
  }
37
50
 
38
51
  async generatePoll(jid, opts) {
39
- const { generatePollMessage } = await import('@whiskeysockets/baileys')
40
- return generatePollMessage(jid, opts)
52
+ const adapter = await this.#fn()
53
+ return adapter.generatePollMessage(jid, opts)
41
54
  }
42
55
 
43
56
  async generateReaction(jid, opts) {
44
- const { generateReactionMessage } = await import('@whiskeysockets/baileys')
45
- return generateReactionMessage(jid, opts)
57
+ const adapter = await this.#fn()
58
+ return adapter.generateReactionMessage(jid, opts)
46
59
  }
47
60
 
48
61
  async relayMessage(jid, message, opts = {}) {
@@ -65,8 +78,11 @@ export class Connection {
65
78
  this.#socket.ev.off(event, handler)
66
79
  }
67
80
 
68
- get raw() {
69
- return this.#socket
81
+ get raw() { return this.#socket }
82
+
83
+ async adapterSource() {
84
+ const adapter = await this.#fn()
85
+ return adapter.source
70
86
  }
71
87
  }
72
88
 
@@ -1,9 +1,4 @@
1
- *
2
- *
3
- *
4
-
5
1
  import { PassThrough, Readable } from 'node:stream'
6
-
7
2
  import { MediaError } from '../errors.js'
8
3
  import { resize } from './image.js'
9
4
 
@@ -16,7 +11,7 @@ async function loadFfmpeg() {
16
11
  return (await import('fluent-ffmpeg')).default
17
12
  } catch (err) {
18
13
  throw new MediaError(
19
- "fluent-ffmpeg is not installed. Install it with `npm i fluent-ffmpeg` (and ffmpeg itself) to use video thumbnails.",
14
+ 'fluent-ffmpeg is not installed. Run: npm i fluent-ffmpeg',
20
15
  { code: 'LUMINA_MEDIA_FFMPEG_MISSING', cause: err },
21
16
  )
22
17
  }
@@ -25,8 +20,6 @@ async function loadFfmpeg() {
25
20
  return ffmpegPromise
26
21
  }
27
22
 
28
- *
29
- *
30
23
  export function getMp4Duration(buffer, opts = {}) {
31
24
  const { silent = true } = opts
32
25
 
@@ -36,16 +29,12 @@ export function getMp4Duration(buffer, opts = {}) {
36
29
  }
37
30
 
38
31
  try {
39
- if (!Buffer.isBuffer(buffer) || buffer.length < 8) {
40
- return fail('invalid buffer: too short')
41
- }
32
+ if (!Buffer.isBuffer(buffer) || buffer.length < 8) return fail('invalid buffer: too short')
42
33
 
43
34
  let offset = 0
44
35
  while (offset < buffer.length - 8) {
45
36
  const size = buffer.readUInt32BE(offset)
46
- if (size < 8 || offset + size > buffer.length) {
47
- return fail('invalid atom size')
48
- }
37
+ if (size < 8 || offset + size > buffer.length) return fail('invalid atom size')
49
38
 
50
39
  const type = buffer.toString('ascii', offset + 4, offset + 8)
51
40
  if (type === 'moov') {
@@ -53,9 +42,7 @@ export function getMp4Duration(buffer, opts = {}) {
53
42
  const moovEnd = offset + size
54
43
  while (moovOffset < moovEnd - 8) {
55
44
  const childSize = buffer.readUInt32BE(moovOffset)
56
- if (childSize < 8 || moovOffset + childSize > moovEnd) {
57
- return fail('invalid child atom size')
58
- }
45
+ if (childSize < 8 || moovOffset + childSize > moovEnd) return fail('invalid child atom size')
59
46
  const childType = buffer.toString('ascii', moovOffset + 4, moovOffset + 8)
60
47
  if (childType === 'mvhd') {
61
48
  const version = buffer.readUInt8(moovOffset + 8)
@@ -87,8 +74,6 @@ export function getMp4Duration(buffer, opts = {}) {
87
74
  }
88
75
  }
89
76
 
90
-
91
- *
92
77
  export async function extractThumbnail(videoBuffer, opts = {}) {
93
78
  const {
94
79
  time,
@@ -105,10 +90,14 @@ export async function extractThumbnail(videoBuffer, opts = {}) {
105
90
  return new Promise((resolve, reject) => {
106
91
  const fail = (err) => {
107
92
  if (silent) return resolve(result === 'base64' ? '' : Buffer.alloc(0))
108
- reject(err instanceof MediaError ? err : new MediaError(err?.message ?? String(err), {
109
- code: 'LUMINA_MEDIA_FFMPEG_FAILED',
110
- cause: err,
111
- }))
93
+ reject(
94
+ err instanceof MediaError
95
+ ? err
96
+ : new MediaError(err?.message ?? String(err), {
97
+ code: 'LUMINA_MEDIA_FFMPEG_FAILED',
98
+ cause: err,
99
+ }),
100
+ )
112
101
  }
113
102
 
114
103
  if (!Buffer.isBuffer(videoBuffer) || videoBuffer.length === 0) {
@@ -130,9 +119,7 @@ export async function extractThumbnail(videoBuffer, opts = {}) {
130
119
  if (frame.length === 0) {
131
120
  return fail(new MediaError('ffmpeg produced empty output', { code: 'LUMINA_MEDIA_FFMPEG_EMPTY' }))
132
121
  }
133
- if (resizeOutput) {
134
- frame = await resize(frame, { width, height, fit: 'cover', format })
135
- }
122
+ if (resizeOutput) frame = await resize(frame, { width, height, fit: 'cover', format })
136
123
  resolve(result === 'base64' ? frame.toString('base64') : frame)
137
124
  } catch (err) {
138
125
  fail(err)
@@ -143,10 +130,12 @@ export async function extractThumbnail(videoBuffer, opts = {}) {
143
130
  try {
144
131
  ffmpeg(input)
145
132
  .outputOptions([`-ss ${seekTime}`, '-vframes 1', '-vcodec png', '-f image2pipe'])
146
- .on('error', (err) => fail(new MediaError(`ffmpeg error: ${err.message}`, {
147
- code: 'LUMINA_MEDIA_FFMPEG_FAILED',
148
- cause: err,
149
- })))
133
+ .on('error', (err) =>
134
+ fail(new MediaError(`ffmpeg error: ${err.message}`, {
135
+ code: 'LUMINA_MEDIA_FFMPEG_FAILED',
136
+ cause: err,
137
+ })),
138
+ )
150
139
  .pipe(output, { end: true })
151
140
  } catch (err) {
152
141
  fail(err)
@@ -1,5 +1,3 @@
1
- *
2
- *
3
1
 
4
2
  export const KEYWORDS = {
5
3
  javascript: new Set([
@@ -1,6 +1,3 @@
1
- *
2
- *
3
- *
4
1
 
5
2
  import { HighlightType, HighlightLabel } from '../proto/enums.js'
6
3
  import { KEYWORDS, SLASH_COMMENT_LANGS, HASH_COMMENT_LANGS, BLOCK_COMMENT_LANGS } from './code-tokenizer-keywords.js'
@@ -17,7 +14,6 @@ function identifierChar(lang) {
17
14
  }
18
15
  }
19
16
 
20
- *
21
17
  export function tokenizeCode(code, lang = 'javascript') {
22
18
  if (typeof code !== 'string' || code.length === 0) {
23
19
  return { codeBlock: [], unifiedBlocks: [] }
@@ -1,7 +1,3 @@
1
- *
2
- *
3
- *
4
- *
5
1
 
6
2
  import { promises as fs } from 'node:fs'
7
3
  import path from 'node:path'
@@ -47,7 +43,6 @@ const RE_CONST_REQUIRE =
47
43
  const RE_DESTRUCT_REQUIRE =
48
44
  /(?:const|let|var)\s*\{([^}]+)\}\s*=\s*require\(\s*(['"])([^'"]+)\2\s*\)/g
49
45
 
50
- *
51
46
  export function transformToESM(src) {
52
47
  let out = src
53
48
  out = out.replace(RE_CONST_REQUIRE, (_, name, _q, mod) => `import ${name} from '${mod}'`)
@@ -67,7 +62,6 @@ export function transformToESM(src) {
67
62
  return out
68
63
  }
69
64
 
70
- *
71
65
  export function applyKnownFixes(filename, content) {
72
66
  const applied = []
73
67
  let next = content
@@ -100,7 +94,6 @@ export class ProtoUpdater {
100
94
  }
101
95
  }
102
96
 
103
- *
104
97
  async #listJsFiles(dir) {
105
98
  const entries = await fs.readdir(dir, { withFileTypes: true })
106
99
  const out = []
@@ -112,7 +105,6 @@ export class ProtoUpdater {
112
105
  return out
113
106
  }
114
107
 
115
- *
116
108
  async backup() {
117
109
  await fs.mkdir(this.backupDir, { recursive: true })
118
110
  const timestamp = Date.now()
@@ -127,7 +119,6 @@ export class ProtoUpdater {
127
119
  return record
128
120
  }
129
121
 
130
- *
131
122
  async #hashTree(dir) {
132
123
  const files = (await this.#listJsFiles(dir)).sort()
133
124
  const hashes = await Promise.all(
@@ -139,7 +130,6 @@ export class ProtoUpdater {
139
130
  return sha256(hashes.join('\n'))
140
131
  }
141
132
 
142
- *
143
133
  async restore(id) {
144
134
  const record = this.history.find((h) => h.id === id)
145
135
  if (!record) {
@@ -160,7 +150,6 @@ export class ProtoUpdater {
160
150
  await this.restore(last.id)
161
151
  }
162
152
 
163
- *
164
153
  async validate() {
165
154
  const files = await this.#listJsFiles(this.protoPath)
166
155
  const errors = []
@@ -176,7 +165,6 @@ export class ProtoUpdater {
176
165
  return { ok: errors.length === 0, errors }
177
166
  }
178
167
 
179
- *
180
168
  async update(opts = {}) {
181
169
  const autoRollback = opts.autoRollback ?? true
182
170
  const dryRun = opts.dryRun ?? false