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,222 @@
|
|
|
1
|
+
const QRCode = require('qrcode');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
class QRGenerator {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.defaultOptions = {
|
|
8
|
+
errorCorrectionLevel: 'M',
|
|
9
|
+
type: 'image/png',
|
|
10
|
+
quality: 0.92,
|
|
11
|
+
margin: 1,
|
|
12
|
+
color: {
|
|
13
|
+
dark: '#000000',
|
|
14
|
+
light: '#FFFFFF'
|
|
15
|
+
},
|
|
16
|
+
width: 256
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Generate QR code as data URL
|
|
21
|
+
async generateQRDataURL(text, options = {}) {
|
|
22
|
+
try {
|
|
23
|
+
const qrOptions = { ...this.defaultOptions, ...options };
|
|
24
|
+
|
|
25
|
+
const dataURL = await QRCode.toDataURL(text, qrOptions);
|
|
26
|
+
return dataURL;
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.error('QR code generation failed:', error.message);
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Generate QR code as buffer
|
|
34
|
+
async generateQRBuffer(text, options = {}) {
|
|
35
|
+
try {
|
|
36
|
+
const qrOptions = { ...this.defaultOptions, ...options };
|
|
37
|
+
|
|
38
|
+
const buffer = await QRCode.toBuffer(text, qrOptions);
|
|
39
|
+
return buffer;
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.error('QR code generation failed:', error.message);
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Generate QR code and save to file
|
|
47
|
+
async generateQRFile(text, filePath, options = {}) {
|
|
48
|
+
try {
|
|
49
|
+
const qrOptions = { ...this.defaultOptions, ...options };
|
|
50
|
+
|
|
51
|
+
await QRCode.toFile(filePath, text, qrOptions);
|
|
52
|
+
return filePath;
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.error('QR code file generation failed:', error.message);
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Generate QR code with custom colors
|
|
60
|
+
async generateCustomQR(text, colors = {}, options = {}) {
|
|
61
|
+
try {
|
|
62
|
+
const customOptions = {
|
|
63
|
+
...this.defaultOptions,
|
|
64
|
+
color: {
|
|
65
|
+
dark: colors.dark || this.defaultOptions.color.dark,
|
|
66
|
+
light: colors.light || this.defaultOptions.color.light
|
|
67
|
+
},
|
|
68
|
+
...options
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const dataURL = await QRCode.toDataURL(text, customOptions);
|
|
72
|
+
return dataURL;
|
|
73
|
+
} catch (error) {
|
|
74
|
+
console.error('Custom QR code generation failed:', error.message);
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Generate QR code for tracking
|
|
80
|
+
generateTrackingQR(email, campaignId, options = {}) {
|
|
81
|
+
const trackingData = {
|
|
82
|
+
email: email,
|
|
83
|
+
campaign: campaignId,
|
|
84
|
+
timestamp: new Date().toISOString(),
|
|
85
|
+
id: this.generateTrackingId()
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const trackingURL = `https://track.example.com/open?data=${encodeURIComponent(JSON.stringify(trackingData))}`;
|
|
89
|
+
return this.generateQRDataURL(trackingURL, options);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Generate QR code for link obfuscation
|
|
93
|
+
generateObfuscatedQR(originalUrl, options = {}) {
|
|
94
|
+
// Create an obfuscated URL that redirects to the original
|
|
95
|
+
const obfuscatedUrl = this.createObfuscatedUrl(originalUrl);
|
|
96
|
+
return this.generateQRDataURL(obfuscatedUrl, options);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Create obfuscated URL
|
|
100
|
+
createObfuscatedUrl(originalUrl) {
|
|
101
|
+
const obfuscationId = this.generateTrackingId();
|
|
102
|
+
return `https://redirect.example.com/${obfuscationId}?target=${encodeURIComponent(originalUrl)}`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Generate tracking ID
|
|
106
|
+
generateTrackingId() {
|
|
107
|
+
return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Embed QR code in HTML message
|
|
111
|
+
embedQRInMessage(messageHtml, qrDataURL, position = 'bottom', size = 'medium') {
|
|
112
|
+
const sizeMap = {
|
|
113
|
+
small: '100px',
|
|
114
|
+
medium: '150px',
|
|
115
|
+
large: '200px'
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const qrHtml = `
|
|
119
|
+
<div style="text-align: center; margin: 20px 0;">
|
|
120
|
+
<img src="${qrDataURL}" alt="QR Code" style="width: ${sizeMap[size] || '150px'}; height: ${sizeMap[size] || '150px'};" />
|
|
121
|
+
<p style="font-size: 12px; color: #666; margin-top: 5px;">Scan to access content</p>
|
|
122
|
+
</div>`;
|
|
123
|
+
|
|
124
|
+
if (position === 'top') {
|
|
125
|
+
return qrHtml + messageHtml;
|
|
126
|
+
} else if (position === 'middle') {
|
|
127
|
+
// Insert in middle of message
|
|
128
|
+
const middleIndex = Math.floor(messageHtml.length / 2);
|
|
129
|
+
return messageHtml.substring(0, middleIndex) + qrHtml + messageHtml.substring(middleIndex);
|
|
130
|
+
} else {
|
|
131
|
+
// Default: bottom
|
|
132
|
+
return messageHtml + qrHtml;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Generate QR code for multiple purposes
|
|
137
|
+
async generateMultiPurposeQR(data, purpose = 'general', options = {}) {
|
|
138
|
+
let qrText = '';
|
|
139
|
+
|
|
140
|
+
switch (purpose) {
|
|
141
|
+
case 'url':
|
|
142
|
+
qrText = data;
|
|
143
|
+
break;
|
|
144
|
+
case 'email':
|
|
145
|
+
qrText = `mailto:${data}`;
|
|
146
|
+
break;
|
|
147
|
+
case 'phone':
|
|
148
|
+
qrText = `tel:${data}`;
|
|
149
|
+
break;
|
|
150
|
+
case 'wifi':
|
|
151
|
+
qrText = `WIFI:S:${data.ssid};T:${data.type || 'WPA'};P:${data.password};;`;
|
|
152
|
+
break;
|
|
153
|
+
case 'vcard':
|
|
154
|
+
qrText = this.generateVCardQR(data);
|
|
155
|
+
break;
|
|
156
|
+
case 'tracking':
|
|
157
|
+
qrText = this.generateTrackingURL(data);
|
|
158
|
+
break;
|
|
159
|
+
default:
|
|
160
|
+
qrText = data;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return await this.generateQRDataURL(qrText, options);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Generate vCard QR code
|
|
167
|
+
generateVCardQR(contactData) {
|
|
168
|
+
const vcard = `BEGIN:VCARD
|
|
169
|
+
VERSION:3.0
|
|
170
|
+
FN:${contactData.name || ''}
|
|
171
|
+
ORG:${contactData.organization || ''}
|
|
172
|
+
TEL:${contactData.phone || ''}
|
|
173
|
+
EMAIL:${contactData.email || ''}
|
|
174
|
+
URL:${contactData.url || ''}
|
|
175
|
+
ADR:${contactData.address || ''}
|
|
176
|
+
END:VCARD`;
|
|
177
|
+
|
|
178
|
+
return vcard;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Generate tracking URL
|
|
182
|
+
generateTrackingURL(trackingData) {
|
|
183
|
+
const params = new URLSearchParams(trackingData);
|
|
184
|
+
return `https://track.example.com/click?${params.toString()}`;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Validate QR code
|
|
188
|
+
async validateQR(qrDataURL) {
|
|
189
|
+
try {
|
|
190
|
+
// Basic validation - check if it's a valid data URL
|
|
191
|
+
if (!qrDataURL.startsWith('data:image/')) {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Try to decode the QR code
|
|
196
|
+
const buffer = Buffer.from(qrDataURL.split(',')[1], 'base64');
|
|
197
|
+
return buffer.length > 0;
|
|
198
|
+
} catch (error) {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Get QR code statistics
|
|
204
|
+
getQRStats(qrDataURL) {
|
|
205
|
+
try {
|
|
206
|
+
const buffer = Buffer.from(qrDataURL.split(',')[1], 'base64');
|
|
207
|
+
return {
|
|
208
|
+
size: buffer.length,
|
|
209
|
+
format: qrDataURL.split(';')[0].split(':')[1],
|
|
210
|
+
valid: true
|
|
211
|
+
};
|
|
212
|
+
} catch (error) {
|
|
213
|
+
return {
|
|
214
|
+
size: 0,
|
|
215
|
+
format: 'unknown',
|
|
216
|
+
valid: false
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
module.exports = QRGenerator;
|
package/src/sender.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const { ConfigManager, CampaignProcessor } = require('./processor');
|
|
3
|
+
const DocumentGenerator = require('./document-generator');
|
|
4
|
+
|
|
5
|
+
// Example: Simulated email sender (replace with real SMTP/Rust backend)
|
|
6
|
+
async function sendEmailFn({ email, senderEmail, subject, body, attachments, fromName, replyTo, priority }) {
|
|
7
|
+
// Simulate PDF generation for each email (if needed)
|
|
8
|
+
// You can add logic to generate a PDF from the body or an attachment
|
|
9
|
+
if (attachments && attachments.length > 0) {
|
|
10
|
+
for (const att of attachments) {
|
|
11
|
+
if (att.filename.endsWith('.pdf')) {
|
|
12
|
+
// Generate PDF from HTML content
|
|
13
|
+
const docGen = new DocumentGenerator();
|
|
14
|
+
const pdfBuffer = await docGen.htmlToPdf(att.content);
|
|
15
|
+
// In a real sender, attach pdfBuffer as the file content
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
// Simulate sending (replace with real logic)
|
|
20
|
+
console.log(`[SEND] To: ${email} | From: ${senderEmail} | Subject: ${subject}`);
|
|
21
|
+
// Return true for success, false for failure
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function send(options) {
|
|
26
|
+
try {
|
|
27
|
+
const configPath = options.config || './config.json';
|
|
28
|
+
const configManager = new ConfigManager(configPath);
|
|
29
|
+
const campaignProcessor = new CampaignProcessor(configManager);
|
|
30
|
+
const initialized = await campaignProcessor.initialize();
|
|
31
|
+
if (!initialized) {
|
|
32
|
+
console.error('Failed to initialize campaign.');
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
await campaignProcessor.sendCampaign(sendEmailFn);
|
|
36
|
+
} catch (err) {
|
|
37
|
+
console.error('Fatal error in send:', err.message);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
module.exports = { send };
|
package/src/utils.js
ADDED
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const natural = require('natural');
|
|
4
|
+
const { Buffer } = require('buffer');
|
|
5
|
+
|
|
6
|
+
// Date and Time Functions
|
|
7
|
+
function formatDate(date, format) {
|
|
8
|
+
const options = { year: 'numeric', month: '2-digit', day: '2-digit' };
|
|
9
|
+
return date.toLocaleDateString('en-GB', options).split('/').reverse().join('-');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function getCurrentDate() {
|
|
13
|
+
return new Date();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function getFutureDate(days) {
|
|
17
|
+
const date = new Date();
|
|
18
|
+
date.setDate(date.getDate() + days);
|
|
19
|
+
return date;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function getPastDate(weeks) {
|
|
23
|
+
const date = new Date();
|
|
24
|
+
date.setDate(date.getDate() - (weeks * 7));
|
|
25
|
+
return date;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Randomization Functions
|
|
29
|
+
function randomInt(min, max) {
|
|
30
|
+
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function randomString(length, type = '') {
|
|
34
|
+
let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
35
|
+
|
|
36
|
+
if (type === 'alpha') {
|
|
37
|
+
characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
|
38
|
+
} else if (type === 'numeric') {
|
|
39
|
+
characters = '0123456789';
|
|
40
|
+
} else if (type === 'uppercase') {
|
|
41
|
+
characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
|
42
|
+
} else if (type === 'lowercase') {
|
|
43
|
+
characters = 'abcdefghijklmnopqrstuvwxyz';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let result = '';
|
|
47
|
+
const charactersLength = characters.length;
|
|
48
|
+
for (let i = 0; i < length; i++) {
|
|
49
|
+
result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function randomMixedString(length, type = '') {
|
|
55
|
+
return randomString(length, type);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Encoding Functions
|
|
59
|
+
function base64Encode(text) {
|
|
60
|
+
if (typeof text !== 'string') return '';
|
|
61
|
+
return Buffer.from(text).toString('base64');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function base64Decode(text) {
|
|
65
|
+
return Buffer.from(text, 'base64').toString('utf8');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function hexEncode(text) {
|
|
69
|
+
return Buffer.from(text).toString('hex');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function hexDecode(text) {
|
|
73
|
+
return Buffer.from(text, 'hex').toString('utf8');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Text Formatting Functions
|
|
77
|
+
function uppercase(text) {
|
|
78
|
+
return text.toUpperCase();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function lowercase(text) {
|
|
82
|
+
return text.toLowerCase();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function capitalize(text) {
|
|
86
|
+
if (typeof text !== 'string') return '';
|
|
87
|
+
return text.charAt(0).toUpperCase() + text.slice(1).toLowerCase();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function nameCase(text) {
|
|
91
|
+
return text.replace(/\b\w+/g, (txt) => txt.charAt(0).toUpperCase() + txt.slice(1).toLowerCase());
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function sentenceCase(text) {
|
|
95
|
+
return text.charAt(0).toUpperCase() + text.slice(1).toLowerCase();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function detectAndFormatNames(emailName) {
|
|
99
|
+
const tokenizer = new natural.WordTokenizer();
|
|
100
|
+
const tokens = tokenizer.tokenize(emailName.replace(/\d+/g, ''));
|
|
101
|
+
const formattedName = tokens.map(token => token.charAt(0).toUpperCase() + token.slice(1).toLowerCase()).join(' ');
|
|
102
|
+
return formattedName;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Email Functions
|
|
106
|
+
function getDomainFromEmail(email) {
|
|
107
|
+
return email.split('@')[1];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function getEmailName(email) {
|
|
111
|
+
return email.split('@')[0];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function getCompanyName(email) {
|
|
115
|
+
return email.split('@')[1].split('.')[0];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function maskEmail(email) {
|
|
119
|
+
const [username, domain] = email.split('@');
|
|
120
|
+
return `${username.substr(0, 3)}***@${domain.substr(0, 2)}***.${domain.split('.').pop()}`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// File Functions
|
|
124
|
+
async function readContentFromFile(filePath) {
|
|
125
|
+
return new Promise((resolve, reject) => {
|
|
126
|
+
fs.readFile(filePath, 'utf8', (err, data) => {
|
|
127
|
+
if (err) {
|
|
128
|
+
console.error(`Error reading file ${filePath}: ${err.message}`);
|
|
129
|
+
reject(err);
|
|
130
|
+
} else {
|
|
131
|
+
resolve(data);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function copyTemplate(src, dest) {
|
|
138
|
+
try {
|
|
139
|
+
const srcPath = path.join(__dirname, '../templates', src);
|
|
140
|
+
const destPath = path.join(dest, src);
|
|
141
|
+
fs.copyFileSync(srcPath, destPath);
|
|
142
|
+
return true;
|
|
143
|
+
} catch (err) {
|
|
144
|
+
console.error(`Failed to copy template ${src}:`, err);
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Validation Functions
|
|
150
|
+
function isValidEmail(email) {
|
|
151
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
152
|
+
return emailRegex.test(email);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function isValidDomain(domain) {
|
|
156
|
+
const domainRegex = /^[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9]\.[a-zA-Z]{2,}$/;
|
|
157
|
+
return domainRegex.test(domain);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Network Functions
|
|
161
|
+
const faviconCache = {};
|
|
162
|
+
const CACHE_FILE = 'favicons-cache.json';
|
|
163
|
+
const FAVICON_TIMEOUT = 3000; // 3 seconds
|
|
164
|
+
|
|
165
|
+
// Load persistent favicon cache from disk
|
|
166
|
+
function loadFaviconCache() {
|
|
167
|
+
try {
|
|
168
|
+
if (fs.existsSync(CACHE_FILE)) {
|
|
169
|
+
const cacheData = fs.readFileSync(CACHE_FILE, 'utf8');
|
|
170
|
+
const cache = JSON.parse(cacheData);
|
|
171
|
+
Object.assign(faviconCache, cache);
|
|
172
|
+
console.log(`Loaded ${Object.keys(cache).length} cached favicons`);
|
|
173
|
+
}
|
|
174
|
+
} catch (error) {
|
|
175
|
+
console.warn('Failed to load favicon cache:', error.message);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Save favicon cache to disk
|
|
180
|
+
function saveFaviconCache() {
|
|
181
|
+
try {
|
|
182
|
+
fs.writeFileSync(CACHE_FILE, JSON.stringify(faviconCache, null, 2));
|
|
183
|
+
console.log(`Saved ${Object.keys(faviconCache).length} favicons to cache`);
|
|
184
|
+
} catch (error) {
|
|
185
|
+
console.warn('Failed to save favicon cache:', error.message);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Fetch favicon with timeout and fallback
|
|
190
|
+
async function fetchFaviconWithTimeout(domain, timeout = FAVICON_TIMEOUT) {
|
|
191
|
+
const controller = new AbortController();
|
|
192
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
const response = await fetch(`http://localhost:8080/favicon?domain=${encodeURIComponent(domain)}`, {
|
|
196
|
+
signal: controller.signal
|
|
197
|
+
});
|
|
198
|
+
clearTimeout(timeoutId);
|
|
199
|
+
|
|
200
|
+
if (!response.ok) {
|
|
201
|
+
throw new Error(`HTTP ${response.status}`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const data = await response.json();
|
|
205
|
+
return data.faviconUrl || '';
|
|
206
|
+
} catch (error) {
|
|
207
|
+
clearTimeout(timeoutId);
|
|
208
|
+
throw error;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Fallback favicon sources
|
|
213
|
+
async function fetchFaviconFallback(domain) {
|
|
214
|
+
const fallbackSources = [
|
|
215
|
+
`https://www.google.com/s2/favicons?domain=${domain}&sz=32`,
|
|
216
|
+
`https://t1.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=https://${domain}&size=32`,
|
|
217
|
+
`https://favicon.ico/${domain}`,
|
|
218
|
+
`https://${domain}/favicon.ico`
|
|
219
|
+
];
|
|
220
|
+
|
|
221
|
+
for (const source of fallbackSources) {
|
|
222
|
+
try {
|
|
223
|
+
const controller = new AbortController();
|
|
224
|
+
const timeoutId = setTimeout(() => controller.abort(), 2000);
|
|
225
|
+
|
|
226
|
+
const response = await fetch(source, {
|
|
227
|
+
signal: controller.signal,
|
|
228
|
+
headers: { 'User-Agent': 'Mozilla/5.0 (compatible; NoLimit/1.0)' }
|
|
229
|
+
});
|
|
230
|
+
clearTimeout(timeoutId);
|
|
231
|
+
|
|
232
|
+
if (response.ok && response.url) {
|
|
233
|
+
return response.url;
|
|
234
|
+
}
|
|
235
|
+
} catch (error) {
|
|
236
|
+
continue; // Try next source
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return ''; // No favicon found
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Main favicon fetching function with caching
|
|
244
|
+
async function fetchFavicon(domain) {
|
|
245
|
+
if (!domain) return '';
|
|
246
|
+
|
|
247
|
+
// Check cache first
|
|
248
|
+
if (faviconCache[domain]) {
|
|
249
|
+
return faviconCache[domain];
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
let faviconUrl = '';
|
|
253
|
+
|
|
254
|
+
// Try primary source first
|
|
255
|
+
try {
|
|
256
|
+
faviconUrl = await fetchFaviconWithTimeout(domain);
|
|
257
|
+
} catch (error) {
|
|
258
|
+
console.warn(`Primary favicon fetch failed for ${domain}:`, error.message);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// If primary failed, try fallback sources
|
|
262
|
+
if (!faviconUrl) {
|
|
263
|
+
try {
|
|
264
|
+
faviconUrl = await fetchFaviconFallback(domain);
|
|
265
|
+
} catch (error) {
|
|
266
|
+
console.warn(`Fallback favicon fetch failed for ${domain}:`, error.message);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Cache the result (even if empty)
|
|
271
|
+
faviconCache[domain] = faviconUrl;
|
|
272
|
+
|
|
273
|
+
return faviconUrl;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Batch fetch favicons for multiple domains
|
|
277
|
+
async function batchFetchFavicons(domains) {
|
|
278
|
+
const uniqueDomains = [...new Set(domains.filter(domain => domain))];
|
|
279
|
+
const missingDomains = uniqueDomains.filter(domain => !faviconCache[domain]);
|
|
280
|
+
|
|
281
|
+
if (missingDomains.length === 0) {
|
|
282
|
+
console.log('All favicons already cached');
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
console.log(`Fetching favicons for ${missingDomains.length} domains...`);
|
|
287
|
+
|
|
288
|
+
// Fetch in parallel with concurrency limit
|
|
289
|
+
const concurrency = 10;
|
|
290
|
+
const results = [];
|
|
291
|
+
|
|
292
|
+
for (let i = 0; i < missingDomains.length; i += concurrency) {
|
|
293
|
+
const batch = missingDomains.slice(i, i + concurrency);
|
|
294
|
+
const batchPromises = batch.map(async (domain) => {
|
|
295
|
+
try {
|
|
296
|
+
const faviconUrl = await fetchFavicon(domain);
|
|
297
|
+
return { domain, faviconUrl, success: true };
|
|
298
|
+
} catch (error) {
|
|
299
|
+
console.warn(`Failed to fetch favicon for ${domain}:`, error.message);
|
|
300
|
+
return { domain, faviconUrl: '', success: false };
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
const batchResults = await Promise.all(batchPromises);
|
|
305
|
+
results.push(...batchResults);
|
|
306
|
+
|
|
307
|
+
// Small delay between batches to be respectful
|
|
308
|
+
if (i + concurrency < missingDomains.length) {
|
|
309
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Update cache with results
|
|
314
|
+
results.forEach(({ domain, faviconUrl }) => {
|
|
315
|
+
faviconCache[domain] = faviconUrl;
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
const successCount = results.filter(r => r.success && r.faviconUrl).length;
|
|
319
|
+
console.log(`Favicon batch complete: ${successCount}/${missingDomains.length} successful`);
|
|
320
|
+
|
|
321
|
+
// Save cache to disk
|
|
322
|
+
saveFaviconCache();
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Initialize cache on module load
|
|
326
|
+
loadFaviconCache();
|
|
327
|
+
|
|
328
|
+
// Export all functions
|
|
329
|
+
module.exports = {
|
|
330
|
+
// Date and Time
|
|
331
|
+
formatDate,
|
|
332
|
+
getCurrentDate,
|
|
333
|
+
getFutureDate,
|
|
334
|
+
getPastDate,
|
|
335
|
+
|
|
336
|
+
// Randomization
|
|
337
|
+
randomInt,
|
|
338
|
+
randomString,
|
|
339
|
+
randomMixedString,
|
|
340
|
+
|
|
341
|
+
// Encoding
|
|
342
|
+
base64Encode,
|
|
343
|
+
base64Decode,
|
|
344
|
+
hexEncode,
|
|
345
|
+
hexDecode,
|
|
346
|
+
|
|
347
|
+
// Text Formatting
|
|
348
|
+
uppercase,
|
|
349
|
+
lowercase,
|
|
350
|
+
capitalize,
|
|
351
|
+
nameCase,
|
|
352
|
+
sentenceCase,
|
|
353
|
+
detectAndFormatNames,
|
|
354
|
+
|
|
355
|
+
// Email
|
|
356
|
+
getDomainFromEmail,
|
|
357
|
+
getEmailName,
|
|
358
|
+
getCompanyName,
|
|
359
|
+
maskEmail,
|
|
360
|
+
|
|
361
|
+
// File
|
|
362
|
+
readContentFromFile,
|
|
363
|
+
copyTemplate,
|
|
364
|
+
|
|
365
|
+
// Validation
|
|
366
|
+
isValidEmail,
|
|
367
|
+
isValidDomain,
|
|
368
|
+
|
|
369
|
+
// Network
|
|
370
|
+
fetchFavicon,
|
|
371
|
+
batchFetchFavicons,
|
|
372
|
+
loadFaviconCache,
|
|
373
|
+
saveFaviconCache
|
|
374
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"smtp": [
|
|
3
|
+
{
|
|
4
|
+
"host": "smtp.gmail.com",
|
|
5
|
+
"port": 587,
|
|
6
|
+
"secure": false,
|
|
7
|
+
"user": "your-email@gmail.com",
|
|
8
|
+
"pass": "your-app-password"
|
|
9
|
+
}
|
|
10
|
+
],
|
|
11
|
+
"emails_list": {
|
|
12
|
+
"path": "emails.txt"
|
|
13
|
+
},
|
|
14
|
+
"senders_list": {
|
|
15
|
+
"path": "senders.txt"
|
|
16
|
+
},
|
|
17
|
+
"messages_body": {
|
|
18
|
+
"path": "messages.html"
|
|
19
|
+
},
|
|
20
|
+
"configurations": {
|
|
21
|
+
"from_name": "John Doe",
|
|
22
|
+
"from_email": "john.doe@company.com",
|
|
23
|
+
"mail_subject": "Important Update",
|
|
24
|
+
"reply_to": "support@company.com",
|
|
25
|
+
"mail_priority": "3",
|
|
26
|
+
"use_attachment": false,
|
|
27
|
+
"raw_smtp": false,
|
|
28
|
+
"raw_headers": {
|
|
29
|
+
"X-Custom-Header": "Custom Value",
|
|
30
|
+
"X-Mailer": "Custom Mailer v1.0",
|
|
31
|
+
"X-Priority": "1",
|
|
32
|
+
"X-MSMail-Priority": "High",
|
|
33
|
+
"Importance": "high"
|
|
34
|
+
},
|
|
35
|
+
"agent": {
|
|
36
|
+
"is_multi_thread": false,
|
|
37
|
+
"how_many_thread": 1
|
|
38
|
+
},
|
|
39
|
+
"system": {
|
|
40
|
+
"delay_sending": false,
|
|
41
|
+
"delay_sending_seconds": 1
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"attachments": [
|
|
45
|
+
{
|
|
46
|
+
"filename": "document.pdf",
|
|
47
|
+
"path": "attachments/document.pdf",
|
|
48
|
+
"active": false,
|
|
49
|
+
"obfuscate": false,
|
|
50
|
+
"encrypted": false,
|
|
51
|
+
"scripter": false
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
}
|