-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmediaAutoSave.js
More file actions
309 lines (265 loc) · 10.6 KB
/
Copy pathmediaAutoSave.js
File metadata and controls
309 lines (265 loc) · 10.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
// File: mediaAutoSave.js
import fs from "fs"
import path from "path"
import { downloadContentFromMessage } from "@whiskeysockets/baileys"
const BASE_MEDIA_DIR = path.join(path.sep, "HASYIM56")
const MEDIA_SAVE_DIR = path.join(BASE_MEDIA_DIR, "auto_save")
// Media type mapping to folder names
const MEDIA_FOLDERS = {
imageMessage: "images",
videoMessage: "videos",
audioMessage: "audios",
documentMessage: "documents",
stickerMessage: "stickers",
ephemeralMessage: "ephemeral",
viewOnceMessage: "viewonce",
viewOnceMessageV2: "viewonce_v2",
pttMessage: "voice_notes",
quotedMessage: "quoted",
}
// Initialize folder structure
const initializeFolders = () => {
try {
if (!fs.existsSync(MEDIA_SAVE_DIR)) {
fs.mkdirSync(MEDIA_SAVE_DIR, { recursive: true })
console.log(`[MEDIA SAVE] Created base directory: ${MEDIA_SAVE_DIR}`)
}
// Create all media type subdirectories
for (const folderName of Object.values(MEDIA_FOLDERS)) {
const folderPath = path.join(MEDIA_SAVE_DIR, folderName)
if (!fs.existsSync(folderPath)) {
fs.mkdirSync(folderPath, { recursive: true })
console.log(`[MEDIA SAVE] Created subfolder: ${folderPath}`)
}
}
} catch (err) {
console.error("[MEDIA SAVE] Failed to initialize folders:", err?.message || err)
}
}
// Detect file extension based on MIME type or magic bytes
const detectFileExtension = (buffer, mimeType = "") => {
try {
if (!buffer || buffer.length < 4) return ".bin"
// Check magic bytes / file signatures
const magic = buffer.slice(0, 12)
// MIME type based detection
if (mimeType) {
if (mimeType.includes("jpeg") || mimeType.includes("jpg")) return ".jpg"
if (mimeType.includes("png")) return ".png"
if (mimeType.includes("webp")) return ".webp"
if (mimeType.includes("gif")) return ".gif"
if (mimeType.includes("mp4")) return ".mp4"
if (mimeType.includes("mpeg")) return ".mp3"
if (mimeType.includes("ogg")) return ".ogg"
if (mimeType.includes("wav")) return ".wav"
if (mimeType.includes("m4a")) return ".m4a"
if (mimeType.includes("pdf")) return ".pdf"
if (mimeType.includes("zip") || mimeType.includes("rar")) return ".zip"
}
// Magic byte detection
if (magic[0] === 0xff && magic[1] === 0xd8) return ".jpg" // JPG
if (magic[0] === 0x89 && magic[1] === 0x50) return ".png" // PNG
if (magic[0] === 0x52 && magic[1] === 0x49 && magic[2] === 0x46 && magic[3] === 0x46) return ".wav" // WAV/RIFF
if (magic[0] === 0x49 && magic[1] === 0x44 && magic[2] === 0x33) return ".mp3" // MP3 ID3
if (magic[0] === 0xff && (magic[1] === 0xfb || magic[1] === 0xf3)) return ".mp3" // MP3 raw
if (magic[0] === 0x4f && magic[1] === 0x67 && magic[2] === 0x67) return ".ogg" // OGG
if (magic[0] === 0x52 && magic[1] === 0x49 && magic[2] === 0x46 && magic[3] === 0x46 && buffer.slice(8, 12).toString() === "WEBP") return ".webp" // WEBP
if (magic[0] === 0x47 && magic[1] === 0x49 && magic[2] === 0x46) return ".gif" // GIF
if (magic[0] === 0x25 && magic[1] === 0x50 && magic[2] === 0x44 && magic[3] === 0x46) return ".pdf" // PDF
if (magic[0] === 0x50 && magic[1] === 0x4b) return ".zip" // ZIP/DOCX
if (magic[0] === 0x1f && magic[1] === 0x8b) return ".gz" // GZIP
// fallback
return ".bin"
} catch (e) {
console.warn("[MEDIA SAVE] Extension detection failed:", e?.message || e)
return ".bin"
}
}
// Normalize date string for folder organization
const getDateFolder = () => {
const now = new Date()
const year = now.getFullYear()
const month = String(now.getMonth() + 1).padStart(2, "0")
const day = String(now.getDate()).padStart(2, "0")
return `${year}-${month}-${day}`
}
// Generate unique filename
const generateFilename = (originalName = "", extension = ".bin") => {
const timestamp = Date.now()
const random = Math.random().toString(36).substring(2, 8)
const sanitized = originalName
? originalName.replace(/[^a-zA-Z0-9.-]/g, "_").substring(0, 50)
: `media_${timestamp}`
return `${sanitized}_${timestamp}_${random}${extension}`
}
// Download and save media
const downloadAndSaveMedia = async (sock, messageNode, mediaType, mediaObject, sender, remoteJid) => {
try {
// Determine download type and folder
const folderName = MEDIA_FOLDERS[mediaType] || "unknown"
const mimeType = mediaObject?.mimetype || ""
// Infer download type
let downloadType = "buffer"
if (mediaType === "imageMessage" || mediaType === "stickerMessage") downloadType = "image"
else if (mediaType === "videoMessage") downloadType = "video"
else if (mediaType === "audioMessage" || mediaType === "pttMessage") downloadType = "audio"
else if (mediaType === "documentMessage") downloadType = "document"
// Download stream
let stream
try {
stream = await downloadContentFromMessage(mediaObject, downloadType)
} catch (downloadErr) {
console.warn(`[MEDIA SAVE] Failed to create download stream for ${mediaType}:`, downloadErr?.message || downloadErr)
return null
}
if (!stream) {
console.warn(`[MEDIA SAVE] No download stream available for ${mediaType}`)
return null
}
// Accumulate buffer from stream
let buffer = Buffer.from([])
try {
for await (const chunk of stream) {
buffer = Buffer.concat([buffer, chunk])
// Safety: limit to 100MB per file
if (buffer.length > 100 * 1024 * 1024) {
console.warn(`[MEDIA SAVE] Media exceeded 100MB limit for ${mediaType}, truncating`)
break
}
}
} catch (streamErr) {
console.warn(`[MEDIA SAVE] Error reading stream for ${mediaType}:`, streamErr?.message || streamErr)
return null
}
if (!buffer || buffer.length === 0) {
console.warn(`[MEDIA SAVE] Downloaded buffer is empty for ${mediaType}`)
return null
}
// Detect extension
const extension = detectFileExtension(buffer, mimeType)
// Generate filename
const originalName = mediaObject?.fileName || mediaObject?.filename || ""
const filename = generateFilename(originalName, extension)
// Create date-based subfolder
const dateFolder = getDateFolder()
const senderFolder = String(sender).replace(/[^0-9]/g, "")
const folderPath = path.join(MEDIA_SAVE_DIR, folderName, dateFolder, senderFolder)
// Ensure folder exists
try {
if (!fs.existsSync(folderPath)) {
fs.mkdirSync(folderPath, { recursive: true })
}
} catch (mkdirErr) {
console.error(`[MEDIA SAVE] Failed to create directory ${folderPath}:`, mkdirErr?.message || mkdirErr)
return null
}
// Write file
const filepath = path.join(folderPath, filename)
try {
fs.writeFileSync(filepath, buffer)
console.log(`[MEDIA SAVE] Saved ${mediaType}: ${filepath} (${(buffer.length / 1024).toFixed(2)} KB)`)
return {
success: true,
filepath,
size: buffer.length,
mediaType,
extension,
sender,
timestamp: Date.now(),
}
} catch (writeErr) {
console.error(`[MEDIA SAVE] Failed to write file ${filepath}:`, writeErr?.message || writeErr)
return null
}
} catch (err) {
console.error("[MEDIA SAVE] Unexpected error in downloadAndSaveMedia:", err?.message || err)
return null
}
}
// Normalize message object to handle ephemeral/viewOnce messages
const normalizeMessage = (message) => {
try {
if (!message) return null
// Handle ephemeral messages
if (message.ephemeralMessage?.message) {
return message.ephemeralMessage.message
}
// Handle view-once messages (V2)
if (message.viewOnceMessageV2?.message) {
return message.viewOnceMessageV2.message
}
// Handle view-once messages (V1)
if (message.viewOnceMessage?.message) {
return message.viewOnceMessage.message
}
return message
} catch (e) {
console.warn("[MEDIA SAVE] Error normalizing message:", e?.message || e)
return message
}
}
// Main handler: register media auto-save
const registerMediaAutoSave = (sock, opts = {}) => {
try {
const {
enabled = true,
logger = console,
includeBotMessages = true, // Include messages sent by the bot itself
} = opts
if (!enabled) {
console.log("[MEDIA SAVE] Media auto-save is disabled")
return
}
// Initialize folders on startup
initializeFolders()
// Register listener
if (!sock.ev || typeof sock.ev.on !== "function") {
console.warn("[MEDIA SAVE] Socket event emitter not available")
return
}
sock.ev.on("messages.upsert", async ({ messages, type }) => {
try {
if (type !== "notify") return
if (!Array.isArray(messages) || messages.length === 0) return
for (const msg of messages) {
try {
if (!msg || !msg.key || !msg.message) continue
// Skip status updates
if (msg.key.remoteJid === "status@broadcast") continue
// Extract sender information
const from = msg.key.remoteJid
const isGroup = String(from || "").endsWith("@g.us")
const sender = isGroup ? msg.key.participant || from : from
const isFromBot = msg.key.fromMe === true
// Skip own messages if includeBotMessages is false
if (isFromBot && !includeBotMessages) continue
// Normalize message (handle ephemeral, viewOnce, etc)
const normalizedMsg = normalizeMessage(msg.message)
if (!normalizedMsg) continue
// Get all media types in this message
const mediaTypes = Object.keys(normalizedMsg)
for (const mediaType of mediaTypes) {
// Check if it's a supported media type
if (!MEDIA_FOLDERS[mediaType]) continue
const mediaObject = normalizedMsg[mediaType]
if (!mediaObject) continue
// Download and save
const result = await downloadAndSaveMedia(sock, msg, mediaType, mediaObject, sender, from)
if (result) {
logger?.log?.(`[MEDIA SAVE] Successfully saved ${mediaType} from ${String(sender).split("@")[0]}`)
}
}
} catch (msgErr) {
logger?.warn?.(`[MEDIA SAVE] Error processing individual message:`, msgErr?.message || msgErr)
}
}
} catch (upsertErr) {
logger?.error?.("[MEDIA SAVE] Error in messages.upsert handler:", upsertErr?.message || upsertErr)
}
})
console.log("[MEDIA SAVE] Auto-save system initialized and listening for media")
} catch (err) {
console.error("[MEDIA SAVE] Failed to register media auto-save:", err?.message || err)
}
}
export default registerMediaAutoSave