@c4t4/heyamigo 0.8.5 → 0.8.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.
@@ -1,3 +1,4 @@
1
+ import { unlink } from 'fs/promises';
1
2
  import { getContentType, isJidGroup, jidDecode, jidNormalizedUser, } from 'baileys';
2
3
  import { getSession } from '../ai/sessions.js';
3
4
  import { config } from '../config.js';
@@ -77,15 +78,30 @@ async function processMessages(messages, sock, ownerJid, isHistorySync = false)
77
78
  const size = getMediaSize(msg);
78
79
  if (size !== null && size > limits.maxFileBytes) {
79
80
  await append(stored);
80
- const mb = (limits.maxFileBytes / (1024 * 1024)).toFixed(1);
81
81
  const quoted = isGroup && config.reply.quoteInGroups ? msg : undefined;
82
- await sendText(sock, stored.jid, `File too large (max ${mb} MB). I can't read this one try a smaller version.`, quoted).catch((err) => logger.error({ err, jid: stored.jid }, 'failed to send oversized-file notice'));
82
+ await sendText(sock, stored.jid, 'Could not process that, please try a smaller file.', quoted).catch((err) => logger.error({ err, jid: stored.jid }, 'failed to send oversized-file notice'));
83
83
  logger.info({ ...logCtx, size, cap: limits.maxFileBytes }, 'oversized media rejected');
84
84
  continue;
85
85
  }
86
86
  }
87
87
  // Download media if present (image, video, audio, document)
88
88
  const media = await downloadAndSave(msg, stored.jid);
89
+ // Post-download safety net: re-check against the real buffer size.
90
+ // Catches cases the pre-download gate missed — protobuf fileLength
91
+ // missing, nested in documentWithCaptionMessage, stickers, etc.
92
+ // Only enforced when we'd otherwise respond; silent groups keep the
93
+ // archive intact regardless of size.
94
+ if (media &&
95
+ limits.maxFileBytes !== null &&
96
+ decision.respond &&
97
+ media.bytes > limits.maxFileBytes) {
98
+ await unlink(media.mediaPath).catch(() => undefined);
99
+ await append(stored);
100
+ const quoted = isGroup && config.reply.quoteInGroups ? msg : undefined;
101
+ await sendText(sock, stored.jid, 'Could not process that, please try a smaller file.', quoted).catch((err) => logger.error({ err, jid: stored.jid }, 'failed to send oversized-file notice'));
102
+ logger.info({ ...logCtx, bytes: media.bytes, cap: limits.maxFileBytes }, 'oversized media rejected (post-download)');
103
+ continue;
104
+ }
89
105
  if (media) {
90
106
  stored.mediaType = media.mediaType;
91
107
  stored.mediaPath = media.mediaPath;
@@ -98,6 +98,7 @@ export async function downloadAndSave(msg, jid) {
98
98
  mediaType,
99
99
  mediaPath: filePath,
100
100
  mediaMime: mimetype,
101
+ bytes: buffer.length,
101
102
  };
102
103
  }
103
104
  catch (err) {
@@ -99,6 +99,29 @@ function load() {
99
99
  const content = readFileSync(ACCESS_FILE, 'utf-8');
100
100
  return AccessSchema.parse(JSON.parse(content));
101
101
  }
102
+ // Per-field merge: access.json's role definition overrides individual fields
103
+ // of DEFAULT_ROLES, instead of replacing the whole role object. This means
104
+ // adding new role fields (maxFileBytes, dailyTokenLimit, …) in code stays
105
+ // effective for existing installs whose access.json predates those fields.
106
+ function resolveRoles() {
107
+ const configured = current.roles ?? {};
108
+ const out = {};
109
+ const names = new Set([
110
+ ...Object.keys(DEFAULT_ROLES),
111
+ ...Object.keys(configured),
112
+ ]);
113
+ for (const name of names) {
114
+ const def = DEFAULT_ROLES[name];
115
+ const cfg = configured[name];
116
+ if (def && cfg)
117
+ out[name] = { ...def, ...cfg };
118
+ else if (cfg)
119
+ out[name] = cfg;
120
+ else if (def)
121
+ out[name] = def;
122
+ }
123
+ return out;
124
+ }
102
125
  function save(next) {
103
126
  writeFileSync(ACCESS_FILE, JSON.stringify(next, null, 2) + '\n', 'utf-8');
104
127
  current = next;
@@ -128,7 +151,7 @@ export function canSendProactive(jid) {
128
151
  }
129
152
  export function getRole(senderNumber) {
130
153
  const users = current.users ?? {};
131
- const roles = { ...DEFAULT_ROLES, ...(current.roles ?? {}) };
154
+ const roles = resolveRoles();
132
155
  const entry = users[senderNumber];
133
156
  if (entry) {
134
157
  const roleName = entry.role;
@@ -154,7 +177,7 @@ export function getRole(senderNumber) {
154
177
  }
155
178
  export function getRoleForContext(senderNumber, isGroup) {
156
179
  const users = current.users ?? {};
157
- const roles = { ...DEFAULT_ROLES, ...(current.roles ?? {}) };
180
+ const roles = resolveRoles();
158
181
  const entry = users[senderNumber];
159
182
  if (entry) {
160
183
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@c4t4/heyamigo",
3
- "version": "0.8.5",
3
+ "version": "0.8.7",
4
4
  "description": "WhatsApp AI bot powered by Claude with long-term memory, browser control, and role-based access",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",