@xbibzlibrary/whatsbibz 1.3.2 → 1.4.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.
@@ -0,0 +1,120 @@
1
+ // BibzWhats — banner ASCII "WhatsBibz." yang dicetak ke terminal saat client dibuat.
2
+ //
3
+ // • Huruf : "Small Slant" (figlet / smslant) — dirender sekali dan disimpan
4
+ // sebagai string statis, jadi TIDAK butuh dependency figlet saat runtime.
5
+ // • Warna : filter pelangi (gradien hue mengalir dari kiri ke kanan), gaya
6
+ // truecolor (24-bit) dengan fallback palet 256 bila terminal tidak
7
+ // mendukungnya, dan otomatis tanpa warna (plain) bila stdout bukan TTY.
8
+ // • Matikan : NO_COLOR=1, atau opsi client `banner: false`.
9
+
10
+ // Baris asli hasil figlet.textSync('WhatsBibz.', { font: 'Small Slant' }).
11
+ // Perhatikan: sebagian besar glyph saling berimpit karena font-nya miring rapat —
12
+ // itu memang karakteristik "Small Slant".
13
+ const LINES = [
14
+ " _ ____ __ ___ _ __ ",
15
+ " | | /| / / / ___ _/ /____ / _ )(_) / ___ ",
16
+ " | |/ |/ / _ \\/ _ `/ __(_-</ _ / / _ \\/_ /_ ",
17
+ " |__/|__/_//_/\\_,_/\\__/___/____/_/_.__//__(_)",
18
+ ];
19
+
20
+ export const BANNER_FONT = 'Small Slant';
21
+ export const BANNER_FILTER = 'Rainbow 2';
22
+
23
+ const MAX_WIDTH = Math.max(...LINES.map((l) => l.length));
24
+
25
+ /** Banner polos (tanpa ANSI) — 4 baris, tanpa baris kosong di ujung. */
26
+ export const BANNER_TEXT = LINES.map((l) => l.trimEnd()).join('\n');
27
+
28
+ const RESET = '\x1b[0m';
29
+ const TRUE_COLOR_TERMS = new Set(['truecolor', '24bit']);
30
+ const TRUECOLOR_TERM_PROGRAMS = ['iterm.app', 'wezterm', 'vscode', 'hyper', 'tabby', 'ghostty', 'kitty', 'alacritty'];
31
+
32
+ /** Deteksi warna: hormati NO_COLOR / FORCE_COLOR; default hanya saat stdout TTY. */
33
+ export function colorEnabled({ env = process.env, stream = process.stdout } = {}) {
34
+ const nc = env.NO_COLOR;
35
+ if (nc !== undefined && nc !== '' && nc !== '0') return false;
36
+ const fc = env.FORCE_COLOR;
37
+ if (fc !== undefined && fc !== '' && fc !== '0') return true;
38
+ return !!(stream && stream.isTTY) && env.TERM !== 'dumb';
39
+ }
40
+
41
+ function trueColorSupported(env = process.env) {
42
+ const ct = String(env.COLORTERM || '').toLowerCase();
43
+ if (ct && TRUE_COLOR_TERMS.has(ct)) return true;
44
+ const prog = String(env.TERM_PROGRAM || '').toLowerCase();
45
+ return TRUECOLOR_TERM_PROGRAMS.some((p) => prog.includes(p));
46
+ }
47
+
48
+ function hslToRgb(h, s, l) {
49
+ h = ((h % 360) + 360) % 360;
50
+ const c = (1 - Math.abs(2 * l - 1)) * s;
51
+ const hp = h / 60;
52
+ const x = c * (1 - Math.abs((hp % 2) - 1));
53
+ let r = 0, g = 0, b = 0;
54
+ if (hp < 1) [r, g, b] = [c, x, 0];
55
+ else if (hp < 2) [r, g, b] = [x, c, 0];
56
+ else if (hp < 3) [r, g, b] = [0, c, x];
57
+ else if (hp < 4) [r, g, b] = [0, x, c];
58
+ else if (hp < 5) [r, g, b] = [x, 0, c];
59
+ else [r, g, b] = [c, 0, x];
60
+ const m = l - c / 2;
61
+ return [Math.round((r + m) * 255), Math.round((g + m) * 255), Math.round((b + m) * 255)];
62
+ }
63
+
64
+ // Pendekatan terdekat ke kode palet 256 (cube 6×6×6 atau grayscale).
65
+ const CUBE = [0, 95, 135, 175, 215, 255];
66
+ function nearest256(r, g, b) {
67
+ const map = (v) => CUBE.reduce((best, x) => (Math.abs(x - v) < Math.abs(best - v) ? x : best), CUBE[0]);
68
+ const rr = map(r), gg = map(g), bb = map(b);
69
+ const rgbErr = (rr - r) ** 2 + (gg - g) ** 2 + (bb - b) ** 2;
70
+ const avg = (r + g + b) / 3;
71
+ const grayIdx = Math.min(23, Math.max(0, Math.round((avg - 3) / 10)));
72
+ const grayV = 8 + grayIdx * 10;
73
+ const grayErr = (grayV - r) ** 2 + (grayV - g) ** 2 + (grayV - b) ** 2;
74
+ if (grayErr < rgbErr) return `38;5;${232 + grayIdx}`;
75
+ return `38;5;${16 + 36 * CUBE.indexOf(rr) + 6 * CUBE.indexOf(gg) + CUBE.indexOf(bb)}`;
76
+ }
77
+
78
+ function colorCode(r, g, b, trueColor) {
79
+ return trueColor ? `38;2;${r};${g};${b}` : nearest256(r, g, b);
80
+ }
81
+
82
+ /**
83
+ * Render banner.
84
+ * @param {object} [opts]
85
+ * @param {boolean|'auto'} [opts.color='auto'] true=selalu berwarna, false=polos, 'auto'=ikuti terminal
86
+ * @param {number} [opts.cycles=1] berapa kali spektrum pelangi diulang sepanjang lebar banner
87
+ * @param {object} [opts.env] environment (untuk deteksi warna)
88
+ * @param {object} [opts.stream] stream untuk deteksi TTY (biasanya process.stdout)
89
+ * @returns {string} banner siap dicetak (tanpa newline tambahan)
90
+ */
91
+ export function renderBanner({ color = 'auto', cycles = 1, env = process.env, stream = process.stdout } = {}) {
92
+ const useColor = color === true || (color === 'auto' && colorEnabled({ env, stream }));
93
+ if (!useColor) return BANNER_TEXT;
94
+ const trueColor = trueColorSupported(env);
95
+ return LINES.map((line) => {
96
+ let out = '';
97
+ for (let c = 0; c < line.length; c++) {
98
+ const ch = line[c];
99
+ if (ch === ' ') {
100
+ out += ' ';
101
+ continue;
102
+ }
103
+ const hue = ((c / MAX_WIDTH) * 360 * cycles) % 360;
104
+ const [r, g, b] = hslToRgb(hue, 1, 0.5);
105
+ out += `\x1b[${colorCode(r, g, b, trueColor)}m${ch}${RESET}`;
106
+ }
107
+ return out.trimEnd();
108
+ }).join('\n');
109
+ }
110
+
111
+ /**
112
+ * Cetak banner ke terminal.
113
+ * @param {object} [opts] meneruskan opsi renderBanner + `write`
114
+ * @param {(s:string)=>void} [opts.write] penulis output (default process.stdout.write)
115
+ */
116
+ export function printBanner(opts = {}) {
117
+ const { write = (s) => process.stdout.write(s), ...rest } = opts;
118
+ write(renderBanner(rest) + '\n');
119
+ return true;
120
+ }
@@ -33,6 +33,12 @@ export interface BibzWhatsOptions {
33
33
  logger?: BibzWhatsLogger;
34
34
  /** cetak QR ASCII ke console saat fallback (butuh qrcode-terminal) */
35
35
  printQR?: boolean;
36
+ /**
37
+ * Cetak banner ASCII "WhatsBibz." saat client dibuat.
38
+ * Default (undefined): hanya di terminal interaktif (stdout TTY).
39
+ * true = selalu cetak, false = jangan pernah.
40
+ */
41
+ banner?: boolean;
36
42
  /** opsi tambahan langsung ke makeWASocket (override) */
37
43
  socketConfig?: Partial<SocketConfig>;
38
44
  maxReconnectAttempts?: number;
@@ -55,7 +61,7 @@ export interface BibzWhatsOptions {
55
61
  /** default true: 'ready' dipancarkan untuk setiap socket baru (pertama & tiap reconnect) */
56
62
  readyOnEveryConnect?: boolean;
57
63
  }
58
- export declare const BIBZWHATS_DEFAULTS: Readonly<Required<Omit<BibzWhatsOptions, 'phone' | 'pairingCode' | 'logger' | 'printQR' | 'socketConfig'>>>;
64
+ export declare const BIBZWHATS_DEFAULTS: Readonly<Required<Omit<BibzWhatsOptions, 'phone' | 'pairingCode' | 'logger' | 'printQR' | 'socketConfig' | 'banner'>>>;
59
65
  export declare function wipeAuthDir(dir: string): boolean;
60
66
  export declare function makeSocketNetworkOptions(opts?: { forceIPv4?: boolean }): Partial<SocketConfig>;
61
67
  export declare function sessionWipeReason(status: number | undefined, message?: string): string | null;
@@ -24,6 +24,7 @@ import { Browsers } from '../Utils/browser-utils.js';
24
24
  import { lintIdentity, derivePairingDisplay } from '../Utils/platform-identity.js';
25
25
  import { resolveDeviceIdentity, createIdentityRotator, isIdentityRejection, describeIdentity, clearIdentityState, loadIdentityState, saveIdentityState } from './device-identity.js';
26
26
  import { createPairingController } from './pairing.js';
27
+ import { printBanner } from './banner.js';
27
28
 
28
29
  export const BIBZWHATS_DEFAULTS = Object.freeze({
29
30
  authDir: 'bibzwhats-session',
@@ -106,6 +107,9 @@ function mergeLogger(logger) {
106
107
  * @property {[string,string,string]} [browser] alias lama `identity` (tuple eksplisit)
107
108
  * @property {object} [logger] {info,warn,error,ok,debug}
108
109
  * @property {boolean} [printQR] cetak QR ASCII ke console saat fallback (butuh qrcode-terminal)
110
+ * @property {boolean} [banner] cetak banner ASCII 'WhatsBibz.' saat client dibuat.
111
+ * default: hanya di terminal interaktif (stdout TTY);
112
+ * true = selalu, false = jangan pernah.
109
113
  * @property {object} [socketConfig] opsi tambahan langsung ke makeWASocket (override)
110
114
  * @property {string} [companionPlatformDisplay] override `companion_platform_display` (WA memvalidasi dgn allow-list; default diturunkan otomatis, mis. "Chrome (Mac OS)" / "Chrome (Linux)")
111
115
  * @property {boolean} [readyOnEveryConnect] default true — 'ready' tiap socket baru; false = hanya sekali
@@ -127,6 +131,14 @@ export async function createBibzWhats(options = {}) {
127
131
  const emitter = new EventEmitter();
128
132
  const groupMetadataCache = new Map();
129
133
 
134
+ // Banner ASCII 'WhatsBibz.' (Small Slant, pelangi) — hanya terminal interaktif secara
135
+ // bawaan; `banner: true` memaksa cetak (mis. untuk log/daemon), `banner: false` mematikan.
136
+ const wantBanner =
137
+ opts.banner === true || (opts.banner !== false && !!process.stdout.isTTY);
138
+ if (wantBanner) {
139
+ try { printBanner(); } catch {}
140
+ }
141
+
130
142
  // ── Identitas perangkat tertaut: kustom (opsi/env) atau otomatis (paling stabil) ──
131
143
  const resolved = resolveDeviceIdentity({ identity: opts.identity === 'auto' ? undefined : opts.identity, browser: opts.browser, authDir: opts.authDir });
132
144
  for (const note of resolved.notes) (note.level === 'warn' ? log.warn : log.info)(`BibzWhats: ${note.message}`);
@@ -4,4 +4,5 @@ export * from './device-identity.js';
4
4
  export * from './send.js';
5
5
  export * from './extract.js';
6
6
  export * from './jid.js';
7
+ export * from './banner.js';
7
8
  export { BIBZWHATS_VERSION, BIBZWHATS_NAME, BIBZWHATS_PACKAGE, BIBZWHATS_UPSTREAM, BIBZWHATS_WA_WEB_VERSION } from './version.js';
@@ -5,4 +5,5 @@ export * from './device-identity.js';
5
5
  export * from './send.js';
6
6
  export * from './extract.js';
7
7
  export * from './jid.js';
8
+ export * from './banner.js';
8
9
  export { BIBZWHATS_VERSION, BIBZWHATS_NAME, BIBZWHATS_PACKAGE, BIBZWHATS_UPSTREAM, BIBZWHATS_WA_WEB_VERSION } from './version.js';
@@ -1,7 +1,7 @@
1
1
  export const BIBZWHATS_NAME = 'WhatsBibz';
2
2
  /** nama paket di npm */
3
3
  export const BIBZWHATS_PACKAGE = '@xbibzlibrary/whatsbibz';
4
- export const BIBZWHATS_VERSION = '1.3.2';
4
+ export const BIBZWHATS_VERSION = '1.4.0';
5
5
  export const BIBZWHATS_UPSTREAM = 'ourin-baileys@9.0.21 (fork WhiskeySockets/Baileys v7) + patch upstream s/d rc14/master 2026-08 & PR #2769/#2776/#2778/#2789; identitas platform diukur ke server 2026-09-03';
6
6
  /** versi WA Web bawaan (fallback) — diperbarui 2026-09-03 dari web.whatsapp.com */
7
7
  export const BIBZWHATS_WA_WEB_VERSION = [2, 3000, 1046874154];
@@ -813,18 +813,15 @@ class Button extends BaseBuilder {
813
813
  }
814
814
 
815
815
  async build(jid, { ...options } = {}) {
816
- if (this._cardBuilders && this._cardBuilders.length > 0) {
817
- for (const builder of this._cardBuilders) {
818
- this.addCard(await builder._card.toCard());
819
- }
820
- this._cardBuilders = [];
821
- }
816
+ // Konten interactive message = hasil toCard() (body/footer/header + nativeFlowMessage).
817
+ // Sebelumnya memakai variabel `message` yang tidak pernah didefinisikan → ReferenceError.
818
+ const payload = await this.toCard();
822
819
  return generateWAMessageFromContent(
823
820
  jid,
824
821
  {
825
822
  ...this._extraPayload,
826
823
  interactiveMessage: {
827
- ...message,
824
+ ...payload,
828
825
  contextInfo: this._contextInfo,
829
826
  },
830
827
  },
@@ -833,6 +830,7 @@ class Button extends BaseBuilder {
833
830
  }
834
831
 
835
832
  async send(jid, { ...options } = {}) {
833
+ if (this._buttons.length < 1) throw new Error('Button requires at least one button');
836
834
  const msg = await this.build(jid, options);
837
835
 
838
836
  await this.#client.relayMessage(msg.key.remoteJid, msg.message, {
@@ -997,7 +995,17 @@ class Carousel extends BaseBuilder {
997
995
  return this;
998
996
  }
999
997
 
1000
- build(jid, { ...options } = {}) {
998
+ async build(jid, { ...options } = {}) {
999
+ // Konsumsi builder terdaftar lewat .card(cb). Sebelumnya _cardBuilders tidak pernah
1000
+ // dipakai di sini → carousel terkirim dengan 0 kartu (diam-diam rusak).
1001
+ if (this._cardBuilders && this._cardBuilders.length > 0) {
1002
+ const built = [];
1003
+ for (const builder of this._cardBuilders) {
1004
+ built.push(await builder._card.toCard());
1005
+ }
1006
+ this._cardBuilders = [];
1007
+ this.addCard(built);
1008
+ }
1001
1009
  return generateWAMessageFromContent(
1002
1010
  jid,
1003
1011
  {
@@ -1019,6 +1027,15 @@ class Carousel extends BaseBuilder {
1019
1027
  }
1020
1028
 
1021
1029
  async send(jid, { ...options } = {}) {
1030
+ if (this._cardBuilders && this._cardBuilders.length > 0) {
1031
+ const built = [];
1032
+ for (const builder of this._cardBuilders) {
1033
+ built.push(await builder._card.toCard());
1034
+ }
1035
+ this._cardBuilders = [];
1036
+ this.addCard(built);
1037
+ }
1038
+ if (this._cards.length < 1) throw new Error('Carousel requires at least one card');
1022
1039
  const msg = await this.build(jid, options);
1023
1040
 
1024
1041
  await this.#client.relayMessage(msg.key.remoteJid, msg.message, {
@@ -28,9 +28,9 @@ export const Browsers = {
28
28
  linux: browser => ['Linux', browser, '6.12.44'],
29
29
  baileys: browser => ['Baileys', browser, '6.5.0'],
30
30
  /** identitas companion BibzWhats (tampil di daftar perangkat tertaut) */
31
- whatsbibz: browser => ['WhatsBibz', browser, '1.3.2'],
31
+ whatsbibz: browser => ['WhatsBibz', browser, '1.4.0'],
32
32
  /** alias lama */
33
- bibzwhats: browser => ['WhatsBibz', browser, '1.3.2'],
33
+ bibzwhats: browser => ['WhatsBibz', browser, '1.4.0'],
34
34
  windows: browser => ['Windows', browser, '10.0.22631'],
35
35
  /** eksperimental (upstream #2201): companion Android — bisa menerima view-once */
36
36
  android: browser => [browser, 'Android', ''],
@@ -544,6 +544,16 @@ export const captureUnifiedResponse = (msg) => {
544
544
  };
545
545
  };
546
546
  export const generateUnifiedResponseContent = (quoted, captured) => {
547
+ if (
548
+ !captured ||
549
+ !Array.isArray(captured.submessages) ||
550
+ !captured.unifiedResponse?.data
551
+ ) {
552
+ throw new TypeError(
553
+ 'captured wajib berupa hasil captureUnifiedResponse(msg) yang berisi ' +
554
+ 'unifiedResponse.data — contoh: sock.sendUnifiedResponse(jid, quoted, captureUnifiedResponse(m.message))',
555
+ );
556
+ }
547
557
  const ctxInfo = buildRichContextInfo(quoted);
548
558
  return {
549
559
  message: buildBotForwardedMessage(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xbibzlibrary/whatsbibz",
3
- "version": "1.3.2",
3
+ "version": "1.4.0",
4
4
  "description": "WhatsBibz — WhatsApp Web multi-device library for Node.js (maintained Baileys fork): high-level client, custom pairing codes, self-healing sessions, configurable or automatic linked-device identity, complete TypeScript declarations.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",