nolimit-x 1.0.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.
- package/bin/nolimit +3 -0
- package/package.json +18 -0
- package/src/attachment-handler.js +253 -0
- package/src/cli.js +31 -0
- package/src/document-generator.js +275 -0
- package/src/encryption.js +125 -0
- package/src/init.js +79 -0
- package/src/multi-handler.js +184 -0
- package/src/obfuscator.js +350 -0
- package/src/placeholders.js +230 -0
- package/src/processor.js +542 -0
- package/src/qr-generator.js +222 -0
- package/src/sender.js +41 -0
- package/src/utils.js +374 -0
- package/templates/config.json +54 -0
- package/templates/emails.txt +3 -0
- package/templates/messages.html +5 -0
- package/templates/senders.txt +3 -0
- package/test-campaign/config.json +35 -0
- package/test-campaign/emails.txt +3 -0
- package/test-campaign/messages.html +5 -0
- package/test-campaign/senders.txt +3 -0
- package/test-raw-smtp/config.json +56 -0
- package/test-raw-smtp/emails.txt +7 -0
- package/test-raw-smtp/messages.html +5 -0
- package/test-raw-smtp/senders.txt +7 -0
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
const crypto = require('crypto');
|
|
2
|
+
|
|
3
|
+
class Obfuscator {
|
|
4
|
+
constructor() {
|
|
5
|
+
this.encodingMethods = {
|
|
6
|
+
base64: 'base64',
|
|
7
|
+
hex: 'hex',
|
|
8
|
+
rot13: 'rot13',
|
|
9
|
+
caesar: 'caesar',
|
|
10
|
+
binary: 'binary',
|
|
11
|
+
url: 'url',
|
|
12
|
+
html: 'html',
|
|
13
|
+
unicode: 'unicode'
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Main obfuscation method
|
|
18
|
+
obfuscate(text, method = 'base64', options = {}) {
|
|
19
|
+
switch (method) {
|
|
20
|
+
case 'base64':
|
|
21
|
+
return this.base64Encode(text);
|
|
22
|
+
case 'hex':
|
|
23
|
+
return this.hexEncode(text);
|
|
24
|
+
case 'rot13':
|
|
25
|
+
return this.rot13Encode(text);
|
|
26
|
+
case 'caesar':
|
|
27
|
+
return this.caesarCipher(text, options.shift || 3);
|
|
28
|
+
case 'binary':
|
|
29
|
+
return this.binaryEncode(text);
|
|
30
|
+
case 'url':
|
|
31
|
+
return this.urlEncode(text);
|
|
32
|
+
case 'html':
|
|
33
|
+
return this.htmlEncode(text);
|
|
34
|
+
case 'unicode':
|
|
35
|
+
return this.unicodeEncode(text);
|
|
36
|
+
case 'custom':
|
|
37
|
+
return this.customObfuscate(text, options);
|
|
38
|
+
default:
|
|
39
|
+
return this.base64Encode(text);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Deobfuscation method
|
|
44
|
+
deobfuscate(text, method = 'base64', options = {}) {
|
|
45
|
+
switch (method) {
|
|
46
|
+
case 'base64':
|
|
47
|
+
return this.base64Decode(text);
|
|
48
|
+
case 'hex':
|
|
49
|
+
return this.hexDecode(text);
|
|
50
|
+
case 'rot13':
|
|
51
|
+
return this.rot13Decode(text);
|
|
52
|
+
case 'caesar':
|
|
53
|
+
return this.caesarCipher(text, -(options.shift || 3));
|
|
54
|
+
case 'binary':
|
|
55
|
+
return this.binaryDecode(text);
|
|
56
|
+
case 'url':
|
|
57
|
+
return this.urlDecode(text);
|
|
58
|
+
case 'html':
|
|
59
|
+
return this.htmlDecode(text);
|
|
60
|
+
case 'unicode':
|
|
61
|
+
return this.unicodeDecode(text);
|
|
62
|
+
case 'custom':
|
|
63
|
+
return this.customDeobfuscate(text, options);
|
|
64
|
+
default:
|
|
65
|
+
return this.base64Decode(text);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Base64 encoding/decoding
|
|
70
|
+
base64Encode(text) {
|
|
71
|
+
return Buffer.from(text, 'utf8').toString('base64');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
base64Decode(text) {
|
|
75
|
+
return Buffer.from(text, 'base64').toString('utf8');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Hex encoding/decoding
|
|
79
|
+
hexEncode(text) {
|
|
80
|
+
return Buffer.from(text, 'utf8').toString('hex');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
hexDecode(text) {
|
|
84
|
+
return Buffer.from(text, 'hex').toString('utf8');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ROT13 encoding/decoding
|
|
88
|
+
rot13Encode(text) {
|
|
89
|
+
return text.replace(/[a-zA-Z]/g, function(char) {
|
|
90
|
+
return String.fromCharCode((char <= 'Z' ? 90 : 122) >= (char = char.charCodeAt(0) + 13) ? char : char - 26);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
rot13Decode(text) {
|
|
95
|
+
return this.rot13Encode(text); // ROT13 is symmetric
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Caesar cipher
|
|
99
|
+
caesarCipher(text, shift) {
|
|
100
|
+
return text.replace(/[a-zA-Z]/g, function(char) {
|
|
101
|
+
const code = char.charCodeAt(0);
|
|
102
|
+
const isUpperCase = code >= 65 && code <= 90;
|
|
103
|
+
const base = isUpperCase ? 65 : 97;
|
|
104
|
+
const shifted = ((code - base + shift) % 26 + 26) % 26 + base;
|
|
105
|
+
return String.fromCharCode(shifted);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Binary encoding/decoding
|
|
110
|
+
binaryEncode(text) {
|
|
111
|
+
return text.split('').map(char => char.charCodeAt(0).toString(2).padStart(8, '0')).join(' ');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
binaryDecode(text) {
|
|
115
|
+
return text.split(' ').map(binary => String.fromCharCode(parseInt(binary, 2))).join('');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// URL encoding/decoding
|
|
119
|
+
urlEncode(text) {
|
|
120
|
+
return encodeURIComponent(text);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
urlDecode(text) {
|
|
124
|
+
return decodeURIComponent(text);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// HTML encoding/decoding
|
|
128
|
+
htmlEncode(text) {
|
|
129
|
+
return text
|
|
130
|
+
.replace(/&/g, '&')
|
|
131
|
+
.replace(/</g, '<')
|
|
132
|
+
.replace(/>/g, '>')
|
|
133
|
+
.replace(/"/g, '"')
|
|
134
|
+
.replace(/'/g, ''');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
htmlDecode(text) {
|
|
138
|
+
return text
|
|
139
|
+
.replace(/&/g, '&')
|
|
140
|
+
.replace(/</g, '<')
|
|
141
|
+
.replace(/>/g, '>')
|
|
142
|
+
.replace(/"/g, '"')
|
|
143
|
+
.replace(/'/g, "'");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Unicode encoding/decoding
|
|
147
|
+
unicodeEncode(text) {
|
|
148
|
+
return text.split('').map(char => '\\u' + char.charCodeAt(0).toString(16).padStart(4, '0')).join('');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
unicodeDecode(text) {
|
|
152
|
+
return text.replace(/\\u([0-9a-fA-F]{4})/g, (match, hex) => String.fromCharCode(parseInt(hex, 16)));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Custom obfuscation
|
|
156
|
+
customObfuscate(text, options = {}) {
|
|
157
|
+
let result = text;
|
|
158
|
+
|
|
159
|
+
// Apply multiple layers of obfuscation
|
|
160
|
+
if (options.layers) {
|
|
161
|
+
for (const layer of options.layers) {
|
|
162
|
+
result = this.obfuscate(result, layer.method, layer.options);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return result;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Custom deobfuscation
|
|
170
|
+
customDeobfuscate(text, options = {}) {
|
|
171
|
+
let result = text;
|
|
172
|
+
|
|
173
|
+
// Apply multiple layers of deobfuscation in reverse order
|
|
174
|
+
if (options.layers) {
|
|
175
|
+
for (let i = options.layers.length - 1; i >= 0; i--) {
|
|
176
|
+
const layer = options.layers[i];
|
|
177
|
+
result = this.deobfuscate(result, layer.method, layer.options);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return result;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Obfuscate URLs
|
|
185
|
+
obfuscateURL(url, method = 'base64') {
|
|
186
|
+
const obfuscated = this.obfuscate(url, method);
|
|
187
|
+
return `https://redirect.example.com/${obfuscated}`;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Obfuscate email addresses
|
|
191
|
+
obfuscateEmail(email, method = 'base64') {
|
|
192
|
+
const [local, domain] = email.split('@');
|
|
193
|
+
const obfuscatedLocal = this.obfuscate(local, method);
|
|
194
|
+
const obfuscatedDomain = this.obfuscate(domain, method);
|
|
195
|
+
return `${obfuscatedLocal}@${obfuscatedDomain}`;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Obfuscate payloads
|
|
199
|
+
obfuscatePayload(payload, method = 'base64', options = {}) {
|
|
200
|
+
// Add random padding
|
|
201
|
+
const padding = options.padding ? this.generateRandomString(options.padding) : '';
|
|
202
|
+
const fullPayload = padding + payload + padding;
|
|
203
|
+
|
|
204
|
+
return this.obfuscate(fullPayload, method, options);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Generate random string for padding
|
|
208
|
+
generateRandomString(length = 10) {
|
|
209
|
+
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
210
|
+
let result = '';
|
|
211
|
+
for (let i = 0; i < length; i++) {
|
|
212
|
+
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
213
|
+
}
|
|
214
|
+
return result;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Obfuscate JavaScript code
|
|
218
|
+
obfuscateJavaScript(code, options = {}) {
|
|
219
|
+
let obfuscated = code;
|
|
220
|
+
|
|
221
|
+
// Variable name obfuscation
|
|
222
|
+
if (options.obfuscateVariables) {
|
|
223
|
+
obfuscated = this.obfuscateVariableNames(obfuscated);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// String obfuscation
|
|
227
|
+
if (options.obfuscateStrings) {
|
|
228
|
+
obfuscated = this.obfuscateStrings(obfuscated);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Add junk code
|
|
232
|
+
if (options.addJunkCode) {
|
|
233
|
+
obfuscated = this.addJunkCode(obfuscated);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return obfuscated;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Obfuscate variable names
|
|
240
|
+
obfuscateVariableNames(code) {
|
|
241
|
+
// Simple variable name obfuscation
|
|
242
|
+
const varRegex = /\b(var|let|const)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\b/g;
|
|
243
|
+
let counter = 0;
|
|
244
|
+
|
|
245
|
+
return code.replace(varRegex, (match, keyword, varName) => {
|
|
246
|
+
const obfuscatedName = `_${counter++}`;
|
|
247
|
+
// Replace all occurrences of the variable name
|
|
248
|
+
const varNameRegex = new RegExp(`\\b${varName}\\b`, 'g');
|
|
249
|
+
code = code.replace(varNameRegex, obfuscatedName);
|
|
250
|
+
return `${keyword} ${obfuscatedName}`;
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Obfuscate strings
|
|
255
|
+
obfuscateStrings(code) {
|
|
256
|
+
const stringRegex = /"([^"]*)"|'([^']*)'/g;
|
|
257
|
+
|
|
258
|
+
return code.replace(stringRegex, (match, doubleQuote, singleQuote) => {
|
|
259
|
+
const string = doubleQuote || singleQuote;
|
|
260
|
+
const obfuscated = this.base64Encode(string);
|
|
261
|
+
return `atob("${obfuscated}")`;
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Add junk code
|
|
266
|
+
addJunkCode(code) {
|
|
267
|
+
const junkCode = `
|
|
268
|
+
(function(){var _0x${Math.random().toString(36).substring(2, 8)}=${Math.floor(Math.random() * 1000)};})();
|
|
269
|
+
`;
|
|
270
|
+
|
|
271
|
+
return junkCode + code;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Obfuscate HTML content
|
|
275
|
+
obfuscateHTML(html, options = {}) {
|
|
276
|
+
let obfuscated = html;
|
|
277
|
+
|
|
278
|
+
// Obfuscate text content
|
|
279
|
+
if (options.obfuscateText) {
|
|
280
|
+
obfuscated = this.obfuscateTextContent(obfuscated);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Obfuscate attributes
|
|
284
|
+
if (options.obfuscateAttributes) {
|
|
285
|
+
obfuscated = this.obfuscateAttributes(obfuscated);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return obfuscated;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Obfuscate text content in HTML
|
|
292
|
+
obfuscateTextContent(html) {
|
|
293
|
+
return html.replace(/>([^<]+)</g, (match, text) => {
|
|
294
|
+
const obfuscated = this.base64Encode(text.trim());
|
|
295
|
+
return `><script>document.write(atob("${obfuscated}"));</script><`;
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Obfuscate HTML attributes
|
|
300
|
+
obfuscateAttributes(html) {
|
|
301
|
+
return html.replace(/(\w+)="([^"]*)"/g, (match, attr, value) => {
|
|
302
|
+
const obfuscated = this.base64Encode(value);
|
|
303
|
+
return `${attr}="${obfuscated}"`;
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Generate obfuscation key
|
|
308
|
+
generateKey(length = 32) {
|
|
309
|
+
return crypto.randomBytes(length).toString('hex');
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// XOR encryption/decryption
|
|
313
|
+
xorEncrypt(text, key) {
|
|
314
|
+
let result = '';
|
|
315
|
+
for (let i = 0; i < text.length; i++) {
|
|
316
|
+
result += String.fromCharCode(text.charCodeAt(i) ^ key.charCodeAt(i % key.length));
|
|
317
|
+
}
|
|
318
|
+
return this.base64Encode(result);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
xorDecrypt(encryptedText, key) {
|
|
322
|
+
const decoded = this.base64Decode(encryptedText);
|
|
323
|
+
let result = '';
|
|
324
|
+
for (let i = 0; i < decoded.length; i++) {
|
|
325
|
+
result += String.fromCharCode(decoded.charCodeAt(i) ^ key.charCodeAt(i % key.length));
|
|
326
|
+
}
|
|
327
|
+
return result;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Get available encoding methods
|
|
331
|
+
getAvailableMethods() {
|
|
332
|
+
return Object.keys(this.encodingMethods);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Test obfuscation/deobfuscation
|
|
336
|
+
testObfuscation(text, method = 'base64') {
|
|
337
|
+
const obfuscated = this.obfuscate(text, method);
|
|
338
|
+
const deobfuscated = this.deobfuscate(obfuscated, method);
|
|
339
|
+
|
|
340
|
+
return {
|
|
341
|
+
original: text,
|
|
342
|
+
obfuscated: obfuscated,
|
|
343
|
+
deobfuscated: deobfuscated,
|
|
344
|
+
success: text === deobfuscated,
|
|
345
|
+
method: method
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
module.exports = Obfuscator;
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
const utils = require('./utils');
|
|
2
|
+
|
|
3
|
+
// Dynamic placeholder patterns and handlers
|
|
4
|
+
const dynamicPlaceholders = [
|
|
5
|
+
// Email placeholders
|
|
6
|
+
{
|
|
7
|
+
pattern: /\[\[-\s*EMAIL\s*-\]\]/g,
|
|
8
|
+
handler: async (email) => email
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
pattern: /\[\[-\s*EMAIL_NAME\s*-\]\]/g,
|
|
12
|
+
handler: async (email) => {
|
|
13
|
+
const emailName = utils.getEmailName(email);
|
|
14
|
+
const cleanedName = emailName.replace(/[^a-zA-Z]/g, '');
|
|
15
|
+
return utils.detectAndFormatNames(cleanedName);
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
pattern: /\[\[-\s*EMAIL_DOMAIN\s*-\]\]/g,
|
|
20
|
+
handler: async (email) => utils.getDomainFromEmail(email)
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
pattern: /\[\[-\s*COMPANY_NAME\s*-\]\]/g,
|
|
24
|
+
handler: async (email) => utils.getCompanyName(email)
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
// Date and Time placeholders
|
|
28
|
+
{
|
|
29
|
+
pattern: /\[\[-\s*TIME\s*-\]\]/g,
|
|
30
|
+
handler: async () => utils.getCurrentDate().toLocaleTimeString('en-US', { hour12: false })
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
pattern: /\[\[-\s*SECOND\s*-\]\]/g,
|
|
34
|
+
handler: async () => utils.getCurrentDate().getSeconds()
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
pattern: /\[\[-\s*MINUTE\s*-\]\]/g,
|
|
38
|
+
handler: async () => utils.getCurrentDate().getMinutes()
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
pattern: /\[\[-\s*HOUR\s*-\]\]/g,
|
|
42
|
+
handler: async () => utils.getCurrentDate().getHours()
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
pattern: /\[\[-\s*DATE\s*-\]\]/g,
|
|
46
|
+
handler: async () => utils.getCurrentDate().toISOString().split('T')[0]
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
pattern: /\[\[-\s*DATE_LONG\s*-\]\]/g,
|
|
50
|
+
handler: async () => utils.getCurrentDate().toLocaleDateString('en-US', {
|
|
51
|
+
weekday: 'long',
|
|
52
|
+
year: 'numeric',
|
|
53
|
+
month: 'long',
|
|
54
|
+
day: 'numeric'
|
|
55
|
+
})
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
pattern: /\[\[-\s*DATE_PLUS_TIME\s*-\]\]/g,
|
|
59
|
+
handler: async () => utils.getCurrentDate().toLocaleString('en-US')
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
// Future/Past date placeholders
|
|
63
|
+
{
|
|
64
|
+
pattern: /\[\[-\s*DATE_FUTURE_PAST\('future',(\d+),'days'\)\s*-\]\]/g,
|
|
65
|
+
handler: async (match, days) => {
|
|
66
|
+
const futureDate = utils.getFutureDate(parseInt(days));
|
|
67
|
+
return futureDate.toISOString().split('T')[0];
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
pattern: /\[\[-\s*DATE_FUTURE_PAST\('past',(\d+),'weeks'\)\s*-\]\]/g,
|
|
72
|
+
handler: async (match, weeks) => {
|
|
73
|
+
const pastDate = utils.getPastDate(parseInt(weeks));
|
|
74
|
+
return pastDate.toISOString().split('T')[0];
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
// Random placeholders
|
|
79
|
+
{
|
|
80
|
+
pattern: /\[\[-\s*RANDOM_NUM\((\d+)\)\s*-\]\]/g,
|
|
81
|
+
handler: async (match, digits) => {
|
|
82
|
+
const min = Math.pow(10, digits - 1);
|
|
83
|
+
const max = Math.pow(10, digits) - 1;
|
|
84
|
+
return utils.randomInt(min, max).toString();
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
pattern: /\[\[-\s*RANDOM_STR\((\d+),'([a-zA-Z]*)'\)\s*-\]\]/g,
|
|
89
|
+
handler: async (match, length, type) => utils.randomString(parseInt(length), type)
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
pattern: /\[\[-\s*RANDOM_MIX\((\d+),'([a-zA-Z]*)'\)\s*-\]\]/g,
|
|
93
|
+
handler: async (match, length, type) => utils.randomMixedString(parseInt(length), type)
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
// Favicon placeholder with optional custom URL
|
|
97
|
+
{
|
|
98
|
+
pattern: /\[\[-\s*FAVICON(?:\|([^\]]+))?\s*-\]\]/g,
|
|
99
|
+
handler: async (match, customUrl, email) => {
|
|
100
|
+
// If custom URL is provided, use it
|
|
101
|
+
if (customUrl) {
|
|
102
|
+
return customUrl.trim();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Otherwise fetch favicon for the domain
|
|
106
|
+
const domain = utils.getDomainFromEmail(email);
|
|
107
|
+
return await utils.fetchFavicon(domain);
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
// QR Code placeholder
|
|
112
|
+
{
|
|
113
|
+
pattern: /\[\[-\s*QRCODE_URL\s*-\]\]/g,
|
|
114
|
+
handler: async () => 'https://example.com/qrcode.png'
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
// Embedded URL placeholder
|
|
118
|
+
{
|
|
119
|
+
pattern: /\[\[-\s*EMBEDDED_URL\s*-\]\]/g,
|
|
120
|
+
handler: async () => 'https://example.com/embedded-file.pdf'
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
// Text transformation placeholders
|
|
124
|
+
{
|
|
125
|
+
pattern: /\[\[-\s*BASE64_ENCODE\('([^']*)'\)\s*-\]\]/g,
|
|
126
|
+
handler: async (match, text) => utils.base64Encode(text)
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
pattern: /\[\[-\s*BASE64_DECODE\('([^']*)'\)\s*-\]\]/g,
|
|
130
|
+
handler: async (match, text) => utils.base64Decode(text)
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
pattern: /\[\[-\s*UPPERCASE\('([^']*)'\)\s*-\]\]/g,
|
|
134
|
+
handler: async (match, text) => utils.uppercase(text)
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
pattern: /\[\[-\s*CAPITALIZE\('([^']*)'\)\s*-\]\]/g,
|
|
138
|
+
handler: async (match, text) => utils.capitalize(text)
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
pattern: /\[\[-\s*NAMECASE\('([^']*)'\)\s*-\]\]/g,
|
|
142
|
+
handler: async (match, text) => utils.nameCase(text)
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
pattern: /\[\[-\s*SENTENCECASE\('([^']*)'\)\s*-\]\]/g,
|
|
146
|
+
handler: async (match, text) => utils.sentenceCase(text)
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
pattern: /\[\[-\s*LOWERCASE\('([^']*)'\)\s*-\]\]/g,
|
|
150
|
+
handler: async (match, text) => utils.lowercase(text)
|
|
151
|
+
}
|
|
152
|
+
];
|
|
153
|
+
|
|
154
|
+
// Main function to replace placeholders in text
|
|
155
|
+
async function replacePlaceholders(text, email, fromName = '', fromEmail = '') {
|
|
156
|
+
if (!text || typeof text !== 'string') return text;
|
|
157
|
+
|
|
158
|
+
let updatedText = text;
|
|
159
|
+
|
|
160
|
+
// Perform all replacements
|
|
161
|
+
for (const { pattern, handler } of dynamicPlaceholders) {
|
|
162
|
+
const matches = [...updatedText.matchAll(pattern)];
|
|
163
|
+
|
|
164
|
+
for (const match of matches) {
|
|
165
|
+
try {
|
|
166
|
+
const replacement = await handler(match[0], ...match.slice(1), email, fromName, fromEmail);
|
|
167
|
+
updatedText = updatedText.replace(match[0], replacement);
|
|
168
|
+
} catch (error) {
|
|
169
|
+
console.error(`Error processing placeholder ${match[0]}:`, error);
|
|
170
|
+
// Keep the original placeholder if there's an error
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return updatedText;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Function to get all available placeholders
|
|
179
|
+
function getAvailablePlaceholders() {
|
|
180
|
+
return dynamicPlaceholders.map(p => ({
|
|
181
|
+
pattern: p.pattern.source,
|
|
182
|
+
description: getPlaceholderDescription(p.pattern.source)
|
|
183
|
+
}));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Function to get description for a placeholder
|
|
187
|
+
function getPlaceholderDescription(pattern) {
|
|
188
|
+
const descriptions = {
|
|
189
|
+
'EMAIL': 'Target email address',
|
|
190
|
+
'EMAIL_NAME': 'Extracted name from email address',
|
|
191
|
+
'EMAIL_DOMAIN': 'Domain part of email address',
|
|
192
|
+
'COMPANY_NAME': 'Company name extracted from domain',
|
|
193
|
+
'TIME': 'Current time (24-hour format)',
|
|
194
|
+
'SECOND': 'Current second',
|
|
195
|
+
'MINUTE': 'Current minute',
|
|
196
|
+
'HOUR': 'Current hour',
|
|
197
|
+
'DATE': 'Current date (YYYY-MM-DD)',
|
|
198
|
+
'DATE_LONG': 'Current date in long format',
|
|
199
|
+
'DATE_PLUS_TIME': 'Current date and time',
|
|
200
|
+
'DATE_FUTURE_PAST': 'Future or past date',
|
|
201
|
+
'RANDOM_NUM': 'Random number with specified digits',
|
|
202
|
+
'RANDOM_STR': 'Random string with specified length and type',
|
|
203
|
+
'RANDOM_MIX': 'Random mixed string',
|
|
204
|
+
'FAVICON': 'Favicon URL for the target domain',
|
|
205
|
+
'QRCODE_URL': 'QR code URL',
|
|
206
|
+
'EMBEDDED_URL': 'Embedded file URL',
|
|
207
|
+
'BASE64_ENCODE': 'Base64 encode text',
|
|
208
|
+
'BASE64_DECODE': 'Base64 decode text',
|
|
209
|
+
'UPPERCASE': 'Convert text to uppercase',
|
|
210
|
+
'CAPITALIZE': 'Capitalize first letter',
|
|
211
|
+
'NAMECASE': 'Convert to name case',
|
|
212
|
+
'SENTENCECASE': 'Convert to sentence case',
|
|
213
|
+
'LOWERCASE': 'Convert text to lowercase'
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
for (const [key, desc] of Object.entries(descriptions)) {
|
|
217
|
+
if (pattern.includes(key)) {
|
|
218
|
+
return desc;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return 'Dynamic placeholder';
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
module.exports = {
|
|
226
|
+
replacePlaceholders,
|
|
227
|
+
getAvailablePlaceholders,
|
|
228
|
+
getPlaceholderDescription,
|
|
229
|
+
dynamicPlaceholders
|
|
230
|
+
};
|