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
package/bin/nolimit
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "nolimit-x",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Advanced email sender for red team operations",
|
|
5
|
+
"main": "src/cli.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"nolimit": "./src/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
11
|
+
},
|
|
12
|
+
"keywords": ["email", "sender", "red-team", "smtp"],
|
|
13
|
+
"author": "",
|
|
14
|
+
"license": "ISC",
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"commander": "^11.0.0"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
const MessageEncryption = require('./encryption');
|
|
5
|
+
|
|
6
|
+
class AttachmentHandler {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.encryption = new MessageEncryption();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Process attachment based on configuration
|
|
12
|
+
async processAttachment(attachmentConfig, messageContent = '') {
|
|
13
|
+
try {
|
|
14
|
+
if (!attachmentConfig.active) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let content = '';
|
|
19
|
+
let filename = attachmentConfig.filename;
|
|
20
|
+
|
|
21
|
+
// Read attachment content
|
|
22
|
+
if (fs.existsSync(attachmentConfig.path)) {
|
|
23
|
+
content = fs.readFileSync(attachmentConfig.path, 'utf8');
|
|
24
|
+
} else {
|
|
25
|
+
console.warn(`Attachment file not found: ${attachmentConfig.path}`);
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Process filename placeholders
|
|
30
|
+
filename = await this.processFilenamePlaceholders(filename);
|
|
31
|
+
|
|
32
|
+
// Apply obfuscation if enabled
|
|
33
|
+
if (attachmentConfig.obfuscate) {
|
|
34
|
+
content = this.obfuscateContent(content);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Apply encryption if enabled
|
|
38
|
+
if (attachmentConfig.encrypted) {
|
|
39
|
+
const encryptedAttachment = this.encryption.createEncryptedAttachment(content, filename);
|
|
40
|
+
if (encryptedAttachment) {
|
|
41
|
+
return {
|
|
42
|
+
filename: encryptedAttachment.filename,
|
|
43
|
+
content: encryptedAttachment.content,
|
|
44
|
+
contentType: 'application/octet-stream',
|
|
45
|
+
encrypted: true,
|
|
46
|
+
metadata: encryptedAttachment.metadata
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Apply scripter if enabled
|
|
52
|
+
if (attachmentConfig.scripter) {
|
|
53
|
+
content = this.addScriptingCapabilities(content);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Determine content type
|
|
57
|
+
const contentType = this.getContentType(filename);
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
filename: filename,
|
|
61
|
+
content: content,
|
|
62
|
+
contentType: contentType,
|
|
63
|
+
encrypted: false
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.error(`Failed to process attachment ${attachmentConfig.filename}:`, error.message);
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Process filename placeholders
|
|
73
|
+
async processFilenamePlaceholders(filename) {
|
|
74
|
+
// Replace RANDOM_NUM placeholders
|
|
75
|
+
filename = filename.replace(/\[\[-RANDOM_NUM\((\d+)\)-\]\]/g, (match, digits) => {
|
|
76
|
+
const min = Math.pow(10, digits - 1);
|
|
77
|
+
const max = Math.pow(10, digits) - 1;
|
|
78
|
+
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Replace RANDOM_STR placeholders
|
|
82
|
+
filename = filename.replace(/\[\[-RANDOM_STR\((\d+),'([a-zA-Z]*)'\)-\]\]/g, (match, length, type) => {
|
|
83
|
+
return this.generateRandomString(parseInt(length), type);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
return filename;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Generate random string
|
|
90
|
+
generateRandomString(length, type = '') {
|
|
91
|
+
let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
92
|
+
|
|
93
|
+
if (type === 'alpha') {
|
|
94
|
+
characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
|
95
|
+
} else if (type === 'numeric') {
|
|
96
|
+
characters = '0123456789';
|
|
97
|
+
} else if (type === 'uppercase') {
|
|
98
|
+
characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
|
99
|
+
} else if (type === 'lowercase') {
|
|
100
|
+
characters = 'abcdefghijklmnopqrstuvwxyz';
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
let result = '';
|
|
104
|
+
const charactersLength = characters.length;
|
|
105
|
+
for (let i = 0; i < length; i++) {
|
|
106
|
+
result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
|
107
|
+
}
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Obfuscate content
|
|
112
|
+
obfuscateContent(content) {
|
|
113
|
+
// Simple obfuscation techniques
|
|
114
|
+
let obfuscated = content;
|
|
115
|
+
|
|
116
|
+
// Replace common words with similar looking characters
|
|
117
|
+
const obfuscationMap = {
|
|
118
|
+
'a': '@',
|
|
119
|
+
'e': '3',
|
|
120
|
+
'i': '1',
|
|
121
|
+
'o': '0',
|
|
122
|
+
's': '$',
|
|
123
|
+
't': '7'
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// Apply character substitution
|
|
127
|
+
for (const [original, replacement] of Object.entries(obfuscationMap)) {
|
|
128
|
+
obfuscated = obfuscated.replace(new RegExp(original, 'gi'), replacement);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Add random whitespace
|
|
132
|
+
obfuscated = obfuscated.replace(/\s+/g, ' ').trim();
|
|
133
|
+
|
|
134
|
+
return obfuscated;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Add scripting capabilities
|
|
138
|
+
addScriptingCapabilities(content) {
|
|
139
|
+
// Add JavaScript execution capabilities
|
|
140
|
+
const scriptWrapper = `
|
|
141
|
+
<script>
|
|
142
|
+
// Auto-execute on load
|
|
143
|
+
(function() {
|
|
144
|
+
try {
|
|
145
|
+
// Add any custom scripting here
|
|
146
|
+
console.log('Script executed successfully');
|
|
147
|
+
|
|
148
|
+
// Example: Track opening
|
|
149
|
+
if (typeof window !== 'undefined') {
|
|
150
|
+
window.addEventListener('load', function() {
|
|
151
|
+
// Track that the attachment was opened
|
|
152
|
+
console.log('Attachment opened at:', new Date().toISOString());
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
} catch (error) {
|
|
156
|
+
console.error('Script execution failed:', error);
|
|
157
|
+
}
|
|
158
|
+
})();
|
|
159
|
+
</script>
|
|
160
|
+
${content}`;
|
|
161
|
+
|
|
162
|
+
return scriptWrapper;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Get content type based on file extension
|
|
166
|
+
getContentType(filename) {
|
|
167
|
+
const ext = path.extname(filename).toLowerCase();
|
|
168
|
+
|
|
169
|
+
const mimeTypes = {
|
|
170
|
+
'.html': 'text/html',
|
|
171
|
+
'.htm': 'text/html',
|
|
172
|
+
'.txt': 'text/plain',
|
|
173
|
+
'.pdf': 'application/pdf',
|
|
174
|
+
'.doc': 'application/msword',
|
|
175
|
+
'.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
176
|
+
'.rtf': 'application/rtf',
|
|
177
|
+
'.epub': 'application/epub+zip',
|
|
178
|
+
'.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
179
|
+
'.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
180
|
+
'.svg': 'image/svg+xml',
|
|
181
|
+
'.png': 'image/png',
|
|
182
|
+
'.jpg': 'image/jpeg',
|
|
183
|
+
'.jpeg': 'image/jpeg',
|
|
184
|
+
'.gif': 'image/gif',
|
|
185
|
+
'.zip': 'application/zip',
|
|
186
|
+
'.rar': 'application/x-rar-compressed',
|
|
187
|
+
'.7z': 'application/x-7z-compressed'
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
return mimeTypes[ext] || 'application/octet-stream';
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Create EML attachment
|
|
194
|
+
createEMLAttachment(fromEmail, toEmail, subject, messageContent, attachmentName = 'message.eml') {
|
|
195
|
+
const emlContent = `From: ${fromEmail}
|
|
196
|
+
To: ${toEmail}
|
|
197
|
+
Subject: ${subject}
|
|
198
|
+
Date: ${new Date().toUTCString()}
|
|
199
|
+
MIME-Version: 1.0
|
|
200
|
+
Content-Type: text/html; charset=UTF-8
|
|
201
|
+
|
|
202
|
+
${messageContent}`;
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
filename: attachmentName,
|
|
206
|
+
content: emlContent,
|
|
207
|
+
contentType: 'message/rfc822'
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Validate attachment configuration
|
|
212
|
+
validateAttachmentConfig(attachmentConfig) {
|
|
213
|
+
const requiredFields = ['filename', 'path', 'active'];
|
|
214
|
+
|
|
215
|
+
for (const field of requiredFields) {
|
|
216
|
+
if (!(field in attachmentConfig)) {
|
|
217
|
+
console.error(`Missing required field in attachment config: ${field}`);
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (attachmentConfig.active && !fs.existsSync(attachmentConfig.path)) {
|
|
223
|
+
console.warn(`Active attachment file not found: ${attachmentConfig.path}`);
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Get attachment statistics
|
|
231
|
+
getAttachmentStats(attachments) {
|
|
232
|
+
const stats = {
|
|
233
|
+
total: attachments.length,
|
|
234
|
+
active: 0,
|
|
235
|
+
encrypted: 0,
|
|
236
|
+
obfuscated: 0,
|
|
237
|
+
scripted: 0,
|
|
238
|
+
valid: 0
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
for (const attachment of attachments) {
|
|
242
|
+
if (attachment.active) stats.active++;
|
|
243
|
+
if (attachment.encrypted) stats.encrypted++;
|
|
244
|
+
if (attachment.obfuscate) stats.obfuscated++;
|
|
245
|
+
if (attachment.scripter) stats.scripted++;
|
|
246
|
+
if (this.validateAttachmentConfig(attachment)) stats.valid++;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return stats;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
module.exports = AttachmentHandler;
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const { Command } = require("commander");
|
|
3
|
+
const init = require("./init");
|
|
4
|
+
const { send } = require("./sender");
|
|
5
|
+
|
|
6
|
+
const program = new Command();
|
|
7
|
+
|
|
8
|
+
program
|
|
9
|
+
.name("nolimit")
|
|
10
|
+
.version("1.0.0")
|
|
11
|
+
.description("Advanced email sender for red team operations");
|
|
12
|
+
|
|
13
|
+
program
|
|
14
|
+
.command("init")
|
|
15
|
+
.argument("<projectName>", "Name of the project to create")
|
|
16
|
+
.description("Initialize a new email campaign project")
|
|
17
|
+
.action((projectName) => {
|
|
18
|
+
console.log("Init command called with:", projectName);
|
|
19
|
+
init(projectName);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
program
|
|
23
|
+
.command("send")
|
|
24
|
+
.description("Send emails using current configuration")
|
|
25
|
+
.option("-c, --config <path>", "Config file path", "templates/config.json")
|
|
26
|
+
.action((options) => {
|
|
27
|
+
console.log("Send command called with options:", options);
|
|
28
|
+
send(options);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
program.parse();
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const puppeteer = require('puppeteer');
|
|
4
|
+
const { execSync } = require('child_process');
|
|
5
|
+
|
|
6
|
+
class DocumentGenerator {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.browser = null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Initialize browser for PDF generation
|
|
12
|
+
async initBrowser() {
|
|
13
|
+
if (!this.browser) {
|
|
14
|
+
this.browser = await puppeteer.launch({
|
|
15
|
+
headless: true,
|
|
16
|
+
args: ['--no-sandbox', '--disable-setuid-sandbox']
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
return this.browser;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Close browser
|
|
23
|
+
async closeBrowser() {
|
|
24
|
+
if (this.browser) {
|
|
25
|
+
await this.browser.close();
|
|
26
|
+
this.browser = null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Convert HTML to PDF
|
|
31
|
+
async htmlToPdf(htmlContent, options = {}) {
|
|
32
|
+
try {
|
|
33
|
+
const browser = await this.initBrowser();
|
|
34
|
+
const page = await browser.newPage();
|
|
35
|
+
|
|
36
|
+
// Set content
|
|
37
|
+
await page.setContent(htmlContent, { waitUntil: 'networkidle0' });
|
|
38
|
+
|
|
39
|
+
// PDF options
|
|
40
|
+
const pdfOptions = {
|
|
41
|
+
format: options.format || 'A4',
|
|
42
|
+
printBackground: true,
|
|
43
|
+
margin: {
|
|
44
|
+
top: options.marginTop || '1cm',
|
|
45
|
+
right: options.marginRight || '1cm',
|
|
46
|
+
bottom: options.marginBottom || '1cm',
|
|
47
|
+
left: options.marginLeft || '1cm'
|
|
48
|
+
},
|
|
49
|
+
...options
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const pdfBuffer = await page.pdf(pdfOptions);
|
|
53
|
+
await page.close();
|
|
54
|
+
|
|
55
|
+
return pdfBuffer;
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.error('PDF generation failed:', error.message);
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Convert HTML to DOC/DOCX using pandoc
|
|
63
|
+
async htmlToDoc(htmlContent, format = 'docx', options = {}) {
|
|
64
|
+
try {
|
|
65
|
+
// Create temporary HTML file
|
|
66
|
+
const tempHtmlPath = path.join(process.cwd(), 'temp_message.html');
|
|
67
|
+
fs.writeFileSync(tempHtmlPath, htmlContent);
|
|
68
|
+
|
|
69
|
+
// Create output filename
|
|
70
|
+
const outputPath = path.join(process.cwd(), `temp_output.${format}`);
|
|
71
|
+
|
|
72
|
+
// Build pandoc command
|
|
73
|
+
let command = `pandoc "${tempHtmlPath}" -o "${outputPath}"`;
|
|
74
|
+
|
|
75
|
+
// Add format-specific options
|
|
76
|
+
if (format === 'docx') {
|
|
77
|
+
command += ' --reference-doc=template.docx' // if template exists
|
|
78
|
+
} else if (format === 'rtf') {
|
|
79
|
+
command += ' --standalone'
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Execute pandoc
|
|
83
|
+
execSync(command, { stdio: 'pipe' });
|
|
84
|
+
|
|
85
|
+
// Read the generated file
|
|
86
|
+
const docBuffer = fs.readFileSync(outputPath);
|
|
87
|
+
|
|
88
|
+
// Cleanup temporary files
|
|
89
|
+
fs.unlinkSync(tempHtmlPath);
|
|
90
|
+
fs.unlinkSync(outputPath);
|
|
91
|
+
|
|
92
|
+
return docBuffer;
|
|
93
|
+
} catch (error) {
|
|
94
|
+
console.error(`${format.toUpperCase()} generation failed:`, error.message);
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Convert HTML to EPUB
|
|
100
|
+
async htmlToEpub(htmlContent, metadata = {}) {
|
|
101
|
+
try {
|
|
102
|
+
// Create EPUB structure
|
|
103
|
+
const epubContent = this.createEpubStructure(htmlContent, metadata);
|
|
104
|
+
|
|
105
|
+
// For now, return a simple EPUB-like structure
|
|
106
|
+
// In a full implementation, you'd use a proper EPUB library
|
|
107
|
+
return Buffer.from(epubContent, 'utf8');
|
|
108
|
+
} catch (error) {
|
|
109
|
+
console.error('EPUB generation failed:', error.message);
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Create EPUB structure
|
|
115
|
+
createEpubStructure(htmlContent, metadata) {
|
|
116
|
+
const title = metadata.title || 'Document';
|
|
117
|
+
const author = metadata.author || 'NoLimit';
|
|
118
|
+
const publisher = metadata.publisher || 'HTML to EPUB Converter';
|
|
119
|
+
|
|
120
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
121
|
+
<package xmlns="http://www.idpf.org/2007/opf" version="3.0" unique-identifier="uid">
|
|
122
|
+
<metadata xmlns:dc="http://purl.org/dc/elements/1.1/">
|
|
123
|
+
<dc:title>${title}</dc:title>
|
|
124
|
+
<dc:creator>${author}</dc:creator>
|
|
125
|
+
<dc:publisher>${publisher}</dc:publisher>
|
|
126
|
+
<dc:language>en</dc:language>
|
|
127
|
+
<dc:identifier id="uid">urn:uuid:${this.generateUUID()}</dc:identifier>
|
|
128
|
+
</metadata>
|
|
129
|
+
<manifest>
|
|
130
|
+
<item id="nav" href="nav.xhtml" media-type="application/xhtml+xml" properties="nav"/>
|
|
131
|
+
<item id="content" href="content.xhtml" media-type="application/xhtml+xml"/>
|
|
132
|
+
</manifest>
|
|
133
|
+
<spine>
|
|
134
|
+
<itemref idref="nav"/>
|
|
135
|
+
<itemref idref="content"/>
|
|
136
|
+
</spine>
|
|
137
|
+
</package>
|
|
138
|
+
|
|
139
|
+
<!-- content.xhtml -->
|
|
140
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
141
|
+
<html xmlns="http://www.w3.org/1999/xhtml">
|
|
142
|
+
<head>
|
|
143
|
+
<title>${title}</title>
|
|
144
|
+
</head>
|
|
145
|
+
<body>
|
|
146
|
+
${htmlContent}
|
|
147
|
+
</body>
|
|
148
|
+
</html>`;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Convert HTML to XLSX (simple table extraction)
|
|
152
|
+
async htmlToXlsx(htmlContent) {
|
|
153
|
+
try {
|
|
154
|
+
// Extract tables from HTML and convert to XLSX format
|
|
155
|
+
// This is a simplified implementation
|
|
156
|
+
const xlsxContent = this.extractTablesToXlsx(htmlContent);
|
|
157
|
+
return Buffer.from(xlsxContent, 'utf8');
|
|
158
|
+
} catch (error) {
|
|
159
|
+
console.error('XLSX generation failed:', error.message);
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Extract tables from HTML
|
|
165
|
+
extractTablesToXlsx(htmlContent) {
|
|
166
|
+
// Simple table extraction - in a real implementation, you'd use a proper library
|
|
167
|
+
const tableRegex = /<table[^>]*>([\s\S]*?)<\/table>/gi;
|
|
168
|
+
const tables = [];
|
|
169
|
+
let match;
|
|
170
|
+
|
|
171
|
+
while ((match = tableRegex.exec(htmlContent)) !== null) {
|
|
172
|
+
tables.push(match[1]);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Convert to CSV-like format (simplified XLSX)
|
|
176
|
+
let xlsxContent = '<?xml version="1.0" encoding="UTF-8"?>\n';
|
|
177
|
+
xlsxContent += '<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">\n';
|
|
178
|
+
xlsxContent += '<sheets>\n';
|
|
179
|
+
xlsxContent += '<sheet name="Sheet1" sheetId="1" r:id="rId1"/>\n';
|
|
180
|
+
xlsxContent += '</sheets>\n';
|
|
181
|
+
xlsxContent += '</workbook>\n';
|
|
182
|
+
|
|
183
|
+
return xlsxContent;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Convert HTML to PPTX (simple slide generation)
|
|
187
|
+
async htmlToPptx(htmlContent, metadata = {}) {
|
|
188
|
+
try {
|
|
189
|
+
const title = metadata.title || 'Document';
|
|
190
|
+
const author = metadata.author || 'NoLimit';
|
|
191
|
+
|
|
192
|
+
// Create simple PPTX structure
|
|
193
|
+
const pptxContent = this.createPptxStructure(htmlContent, title, author);
|
|
194
|
+
return Buffer.from(pptxContent, 'utf8');
|
|
195
|
+
} catch (error) {
|
|
196
|
+
console.error('PPTX generation failed:', error.message);
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Create PPTX structure
|
|
202
|
+
createPptxStructure(htmlContent, title, author) {
|
|
203
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
204
|
+
<p:presentation xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
|
|
205
|
+
<p:sldMasters>
|
|
206
|
+
<p:sldMaster>
|
|
207
|
+
<p:cSld>
|
|
208
|
+
<p:spTree>
|
|
209
|
+
<p:sp>
|
|
210
|
+
<p:txBody>
|
|
211
|
+
<a:p>
|
|
212
|
+
<a:r>
|
|
213
|
+
<a:t>${title}</a:t>
|
|
214
|
+
</a:r>
|
|
215
|
+
</a:p>
|
|
216
|
+
</p:txBody>
|
|
217
|
+
</p:sp>
|
|
218
|
+
</p:spTree>
|
|
219
|
+
</p:cSld>
|
|
220
|
+
</p:sldMaster>
|
|
221
|
+
</p:sldMasters>
|
|
222
|
+
<p:sldIdLst>
|
|
223
|
+
<p:sld>
|
|
224
|
+
<p:cSld>
|
|
225
|
+
<p:spTree>
|
|
226
|
+
<p:sp>
|
|
227
|
+
<p:txBody>
|
|
228
|
+
<a:p>
|
|
229
|
+
<a:r>
|
|
230
|
+
<a:t>${htmlContent.replace(/<[^>]*>/g, '')}</a:t>
|
|
231
|
+
</a:r>
|
|
232
|
+
</a:p>
|
|
233
|
+
</p:txBody>
|
|
234
|
+
</p:sp>
|
|
235
|
+
</p:spTree>
|
|
236
|
+
</p:cSld>
|
|
237
|
+
</p:sld>
|
|
238
|
+
</p:sldIdLst>
|
|
239
|
+
</p:presentation>`;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Generate UUID for EPUB
|
|
243
|
+
generateUUID() {
|
|
244
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
|
245
|
+
const r = Math.random() * 16 | 0;
|
|
246
|
+
const v = c === 'x' ? r : (r & 0x3 | 0x8);
|
|
247
|
+
return v.toString(16);
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Check if pandoc is available
|
|
252
|
+
isPandocAvailable() {
|
|
253
|
+
try {
|
|
254
|
+
execSync('pandoc --version', { stdio: 'pipe' });
|
|
255
|
+
return true;
|
|
256
|
+
} catch (error) {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Get supported formats
|
|
262
|
+
getSupportedFormats() {
|
|
263
|
+
const formats = ['pdf'];
|
|
264
|
+
|
|
265
|
+
if (this.isPandocAvailable()) {
|
|
266
|
+
formats.push('docx', 'doc', 'rtf');
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
formats.push('epub', 'xlsx', 'pptx');
|
|
270
|
+
|
|
271
|
+
return formats;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
module.exports = DocumentGenerator;
|