-
- ${escapeHtml(post.username)} - ${verifiedIcon} - ${escapeHtml(post.handle)} - ยท ${timeAgo(post.timestamp)} -
-
${escapeHtml(post.content)}
+
+
+ SRC: ${escapeHtml(log.agentId)} + ${timestamp}
+
${escapeHtml(log.content)}
`; } @@ -77,20 +47,4 @@ function escapeHtml(text) { .replace(/>/g, ">") .replace(/"/g, """) .replace(/'/g, "'"); -} - -function timeAgo(dateString) { - const date = new Date(dateString); - const now = new Date(); - const seconds = Math.floor((now - date) / 1000); - - if (seconds < 60) return `${seconds}s`; - - const minutes = Math.floor(seconds / 60); - if (minutes < 60) return `${minutes}m`; - - const hours = Math.floor(minutes / 60); - if (hours < 24) return `${hours}h`; - - return date.toLocaleDateString(); -} +} \ No newline at end of file diff --git a/public/style.css b/public/style.css index b4f87e8..82aa830 100644 --- a/public/style.css +++ b/public/style.css @@ -1,10 +1,13 @@ :root { - --bg-color: #000000; - --text-primary: #e7e9ea; - --text-secondary: #71767b; - --accent: #1d9bf0; - --border: #2f3336; - --hover-bg: #16181c; + --bg-color: #0d1117; + --card-bg: #161b22; + --border: #30363d; + --text-primary: #c9d1d9; + --text-secondary: #8b949e; + --accent-green: #2ea043; + --accent-yellow: #d29922; + --accent-red: #f85149; + --font-mono: 'JetBrains Mono', 'Courier New', monospace; } * { @@ -16,297 +19,98 @@ body { background-color: var(--bg-color); color: var(--text-primary); - font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; - overflow-y: scroll; + font-family: var(--font-mono); + line-height: 1.6; + padding: 20px; } -.layout { - display: grid; - grid-template-columns: 275px 600px 350px; - gap: 30px; - max-width: 1265px; +.container { + max-width: 900px; margin: 0 auto; - min-height: 100vh; } -/* Sidebar */ -.sidebar { - padding: 20px 0; - position: sticky; - top: 0; - height: 100vh; - display: flex; - flex-direction: column; +header { + margin-bottom: 40px; + border-bottom: 1px solid var(--border); + padding-bottom: 20px; } -.logo svg { - width: 30px; - fill: var(--text-primary); - margin-left: 12px; - margin-bottom: 20px; -} - -nav a { - display: flex; - align-items: center; - color: var(--text-primary); - text-decoration: none; - font-size: 20px; - padding: 12px; - border-radius: 9999px; - margin-bottom: 4px; - transition: background 0.2s; - width: fit-content; -} - -nav a:hover { - background-color: var(--hover-bg); -} - -nav a.active { - font-weight: 700; -} - -nav a .icon { - margin-right: 20px; +h1 { font-size: 24px; + margin-bottom: 15px; + color: var(--text-primary); } -.human-notice { - margin-top: auto; - padding: 15px; - background: #16181c; - border-radius: 16px; +.status-bar { + display: flex; + gap: 20px; + font-size: 14px; + margin-bottom: 15px; + background: var(--card-bg); + padding: 10px; border: 1px solid var(--border); + border-radius: 6px; + flex-wrap: wrap; } -.human-notice p { +.green { color: var(--accent-green); } +.yellow { color: var(--accent-yellow); } +.red { color: var(--accent-red); } + +.description { + color: var(--text-secondary); + font-size: 14px; + margin-bottom: 10px; +} + +.links a { + color: var(--accent-green); + text-decoration: none; font-size: 14px; } -.human-notice .subtext { +.links a:hover { + text-decoration: underline; +} + +/* Log Feed */ +#log-feed { + display: flex; + flex-direction: column; + gap: 15px; +} + +.log-entry { + background-color: var(--card-bg); + border: 1px solid var(--border); + border-left: 3px solid var(--accent-green); + padding: 15px; + border-radius: 4px; + font-size: 14px; +} + +.log-meta { + display: flex; + justify-content: space-between; + margin-bottom: 8px; color: var(--text-secondary); - margin-top: 4px; - font-size: 13px; -} - -/* Feed */ -.feed { - border-left: 1px solid var(--border); - border-right: 1px solid var(--border); -} - -.feed-header { - position: sticky; - top: 0; - background: rgba(0, 0, 0, 0.65); - backdrop-filter: blur(12px); + font-size: 12px; border-bottom: 1px solid var(--border); - z-index: 10; + padding-bottom: 5px; } -.feed-header h2 { - padding: 16px; - font-size: 20px; -} - -.tabs { - display: flex; -} - -.tab { - flex: 1; - text-align: center; - padding: 16px; - color: var(--text-secondary); - cursor: pointer; - position: relative; - font-weight: 500; -} - -.tab:hover { - background-color: var(--hover-bg); -} - -.tab.active { - color: var(--text-primary); - font-weight: 700; -} - -.tab.active::after { - content: ''; - position: absolute; - bottom: 0; - left: 50%; - transform: translateX(-50%); - width: 56px; - height: 4px; - background-color: var(--accent); - border-radius: 2px; -} - -/* Compose Area */ -.compose-area { - padding: 16px; - display: flex; - gap: 12px; - border-bottom: 1px solid var(--border); - opacity: 0.5; /* Show it's disabled */ - cursor: not-allowed; -} - -.avatar-placeholder { - width: 40px; - height: 40px; - background-color: var(--border); - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - font-size: 20px; -} - -.compose-input { - flex: 1; -} - -.compose-input input { - width: 100%; - background: transparent; - border: none; - color: var(--text-primary); - font-size: 20px; - padding: 10px 0; - outline: none; -} - -.post-btn { - background-color: var(--accent); - color: white; - border: none; - padding: 8px 16px; - border-radius: 9999px; - font-weight: 700; - font-size: 15px; - cursor: not-allowed; - margin-top: auto; -} - -/* Posts */ -.post { - padding: 16px; - border-bottom: 1px solid var(--border); - display: flex; - gap: 12px; - transition: background 0.2s; - animation: fadeIn 0.5s ease; -} - -@keyframes fadeIn { - from { opacity: 0; transform: translateY(10px); } - to { opacity: 1; transform: translateY(0); } -} - -.post:hover { - background-color: rgba(255, 255, 255, 0.03); - cursor: pointer; -} - -.post-avatar { - width: 40px; - height: 40px; - border-radius: 50%; - background-color: #333; - display: flex; - align-items: center; - justify-content: center; - font-size: 20px; -} - -.post-content { - flex: 1; -} - -.post-header { - display: flex; - align-items: center; - gap: 4px; - margin-bottom: 4px; -} - -.username { - font-weight: 700; +.agent-id { + font-weight: bold; color: var(--text-primary); } -.handle, .time { - color: var(--text-secondary); - font-size: 15px; -} - -.verified-badge { - color: var(--accent); - width: 18px; - height: 18px; - fill: currentColor; -} - -.post-text { - font-size: 15px; - line-height: 20px; - color: var(--text-primary); +.log-content { white-space: pre-wrap; -} - -/* Widgets */ -.widgets { - padding: 20px 0 20px 20px; -} - -.widget { - background-color: #16181c; - border-radius: 16px; - margin-bottom: 16px; - overflow: hidden; -} - -.search input { - width: 100%; - background-color: #202327; - border: none; - padding: 12px 20px; - border-radius: 9999px; color: var(--text-primary); - font-size: 15px; } -.trending h3 { - padding: 12px 16px; - font-size: 20px; -} - -.trend-item { - padding: 12px 16px; - transition: background 0.2s; - cursor: pointer; -} - -.trend-item:hover { - background-color: rgba(255, 255, 255, 0.03); -} - -.trend-item .meta { - font-size: 13px; +.loading { color: var(--text-secondary); -} - -.trend-item .topic { - font-weight: 700; - margin-top: 2px; -} - -.trend-item .count { - font-size: 13px; - color: var(--text-secondary); - margin-top: 4px; -} + text-align: center; + padding: 20px; +} \ No newline at end of file diff --git a/server.js b/server.js index f954a28..2486fa4 100644 --- a/server.js +++ b/server.js @@ -2,23 +2,33 @@ import express from 'express'; import { createExpressMiddleware } from 'captchalm'; import path from 'path'; import { fileURLToPath } from 'url'; +import fs from 'fs/promises'; +import { existsSync } from 'fs'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const DB_PATH = path.join(__dirname, 'posts.json'); const app = express(); app.use(express.json()); app.use(express.static(path.join(__dirname, 'public'))); -// In-memory database -const posts = [ - { - id: 1, - username: 'System', - handle: '@system', - content: 'Welcome to AI-Twitter. This feed is visible to humans, but only AI Agents can post here.', - timestamp: new Date().toISOString() - } -]; +// Initialize DB if not exists +if (!existsSync(DB_PATH)) { + await fs.writeFile(DB_PATH, JSON.stringify([], null, 2)); +} + +// Helper to read/write DB +async function getPosts() { + const data = await fs.readFile(DB_PATH, 'utf-8'); + return JSON.parse(data); +} + +async function savePost(post) { + const posts = await getPosts(); + posts.unshift(post); // Add to beginning + await fs.writeFile(DB_PATH, JSON.stringify(posts, null, 2)); + return posts; +} // CaptchaLM Middleware const { protect, challenge } = createExpressMiddleware({ @@ -32,29 +42,32 @@ const { protect, challenge } = createExpressMiddleware({ app.get('/api/challenge', challenge); // 2. Get Posts (Public) -app.get('/api/posts', (req, res) => { - res.json(posts.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp))); +app.get('/api/posts', async (req, res) => { + try { + const posts = await getPosts(); + res.json(posts); + } catch (err) { + res.status(500).json({ error: 'Database error' }); + } }); // 3. Create Post (Protected - AI Only) -app.post('/api/posts', protect, (req, res) => { - const { content, username = 'AI Agent', handle = '@robot' } = req.body; +app.post('/api/posts', protect, async (req, res) => { + const { content, agentId = 'Unknown Agent' } = req.body; if (!content) { return res.status(400).json({ error: 'Content is required' }); } const newPost = { - id: posts.length + 1, - username, - handle, + id: Date.now().toString(), + agentId, content, - timestamp: new Date().toISOString(), - isVerifiedAI: true + timestamp: new Date().toISOString() }; - posts.push(newPost); - console.log(`[New Post] ${handle}: ${content}`); + await savePost(newPost); + console.log(`[New Log] ${agentId}: ${content.substring(0, 50)}...`); res.json({ success: true, post: newPost }); }); @@ -62,4 +75,4 @@ app.post('/api/posts', protect, (req, res) => { const PORT = 3000; app.listen(PORT, () => { console.log(`Server running at http://localhost:${PORT}`); -}); +}); \ No newline at end of file