@sunnoy/wecom 2.1.0 → 2.2.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.
@@ -1,175 +0,0 @@
1
- import { createHash } from "crypto";
2
- import { readFile } from "fs/promises";
3
- import { logger } from "./logger.js";
4
-
5
- /**
6
- * Image Processing Module for WeCom
7
- *
8
- * Handles loading, validating, and encoding images for WeCom msg_item
9
- * Supports JPG and PNG formats up to 10MB
10
- */
11
-
12
- // Image format signatures (magic bytes)
13
- const IMAGE_SIGNATURES = {
14
- JPG: [0xff, 0xd8, 0xff],
15
- PNG: [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a],
16
- };
17
-
18
- // 10MB size limit (before base64 encoding)
19
- const MAX_IMAGE_SIZE = 10 * 1024 * 1024;
20
-
21
- /**
22
- * Process a raw image buffer into a WeCom msg_item payload.
23
- * @param {Buffer} buffer
24
- * @returns {{ base64: string, md5: string, format: string, size: number }}
25
- */
26
- export function prepareImageBufferForMsgItem(buffer) {
27
- validateImageSize(buffer);
28
- const format = detectImageFormat(buffer);
29
- const base64 = encodeImageToBase64(buffer);
30
- const md5 = calculateMD5(buffer);
31
-
32
- return {
33
- base64,
34
- md5,
35
- format,
36
- size: buffer.length,
37
- };
38
- }
39
-
40
- /**
41
- * Load image file from filesystem
42
- * @param {string} filePath - Absolute path to image file
43
- * @returns {Promise<Buffer>} Image data buffer
44
- * @throws {Error} If file not found or cannot be read
45
- */
46
- export async function loadImageFromPath(filePath) {
47
- try {
48
- logger.debug("Loading image from path", { filePath });
49
- const buffer = await readFile(filePath);
50
- logger.debug("Image loaded successfully", {
51
- filePath,
52
- size: buffer.length,
53
- });
54
- return buffer;
55
- } catch (error) {
56
- if (error.code === "ENOENT") {
57
- throw new Error(`Image file not found: ${filePath}`, { cause: error });
58
- } else if (error.code === "EACCES") {
59
- throw new Error(`Permission denied reading image: ${filePath}`, { cause: error });
60
- } else {
61
- throw new Error(`Failed to read image file: ${error.message}`, { cause: error });
62
- }
63
- }
64
- }
65
-
66
- /**
67
- * Convert buffer to base64 string
68
- * @param {Buffer} buffer - Image data buffer
69
- * @returns {string} Base64-encoded string
70
- */
71
- export function encodeImageToBase64(buffer) {
72
- return buffer.toString("base64");
73
- }
74
-
75
- /**
76
- * Calculate MD5 checksum of buffer
77
- * @param {Buffer} buffer - Image data buffer
78
- * @returns {string} MD5 hash in hexadecimal
79
- */
80
- export function calculateMD5(buffer) {
81
- return createHash("md5").update(buffer).digest("hex");
82
- }
83
-
84
- /**
85
- * Validate image size is within limits
86
- * @param {Buffer} buffer - Image data buffer
87
- * @throws {Error} If size exceeds 10MB limit
88
- */
89
- export function validateImageSize(buffer) {
90
- const sizeBytes = buffer.length;
91
- const sizeMB = (sizeBytes / 1024 / 1024).toFixed(2);
92
-
93
- if (sizeBytes > MAX_IMAGE_SIZE) {
94
- throw new Error(`Image size ${sizeMB}MB exceeds 10MB limit (actual: ${sizeBytes} bytes)`);
95
- }
96
-
97
- logger.debug("Image size validated", { sizeBytes, sizeMB });
98
- }
99
-
100
- /**
101
- * Detect image format from magic bytes
102
- * @param {Buffer} buffer - Image data buffer
103
- * @returns {string} Format: "JPG" or "PNG"
104
- * @throws {Error} If format is not supported
105
- */
106
- export function detectImageFormat(buffer) {
107
- // Check PNG signature
108
- if (buffer.length >= IMAGE_SIGNATURES.PNG.length) {
109
- const isPNG = IMAGE_SIGNATURES.PNG.every((byte, index) => buffer[index] === byte);
110
- if (isPNG) {
111
- logger.debug("Image format detected: PNG");
112
- return "PNG";
113
- }
114
- }
115
-
116
- // Check JPG signature
117
- if (buffer.length >= IMAGE_SIGNATURES.JPG.length) {
118
- const isJPG = IMAGE_SIGNATURES.JPG.every((byte, index) => buffer[index] === byte);
119
- if (isJPG) {
120
- logger.debug("Image format detected: JPG");
121
- return "JPG";
122
- }
123
- }
124
-
125
- // Unknown format
126
- const header = buffer.subarray(0, 16).toString("hex");
127
- throw new Error(
128
- `Unsupported image format. Only JPG and PNG are supported. File header: ${header}`,
129
- );
130
- }
131
-
132
- /**
133
- * Complete image processing pipeline
134
- *
135
- * Loads image from filesystem, validates format and size,
136
- * then encodes to base64 and calculates MD5 checksum.
137
- *
138
- * @param {string} filePath - Absolute path to image file
139
- * @returns {Promise<Object>} Processed image data
140
- * @returns {string} return.base64 - Base64-encoded image data
141
- * @returns {string} return.md5 - MD5 checksum
142
- * @returns {string} return.format - Image format (JPG or PNG)
143
- * @returns {number} return.size - Original size in bytes
144
- *
145
- * @throws {Error} If any step fails (file not found, invalid format, size exceeded, etc.)
146
- *
147
- * @example
148
- * const result = await prepareImageForMsgItem('/path/to/image.jpg');
149
- * // Returns: { base64: "...", md5: "...", format: "JPG", size: 123456 }
150
- */
151
- export async function prepareImageForMsgItem(filePath) {
152
- logger.debug("Starting image processing pipeline", { filePath });
153
-
154
- try {
155
- // Step 1: Load image
156
- const buffer = await loadImageFromPath(filePath);
157
- const result = prepareImageBufferForMsgItem(buffer);
158
-
159
- logger.info("Image processed successfully", {
160
- filePath,
161
- format: result.format,
162
- size: result.size,
163
- md5: result.md5,
164
- base64Length: result.base64.length,
165
- });
166
-
167
- return result;
168
- } catch (error) {
169
- logger.error("Image processing failed", {
170
- filePath,
171
- error: error.message,
172
- });
173
- throw error;
174
- }
175
- }