A Val Town application that formats X.com (formerly Twitter) article-style posts in a clean, aesthetically pleasing reading experience. The application fetches content from the FXTwitter API and displays it with minimal black and white design, optimized for readability.
x.com/user/status/id → subdomain.val.run/user/status/idx.com and twitter.com domainssystem-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif<ul>, <ol>)fileType: "http")GET / → Homepage with input form
GET /:username/status/:id → Formatted article view
https://api.fxtwitter.com/:username/status/:id)CREATE TABLE IF NOT EXISTS articles (
tweet_id TEXT PRIMARY KEY,
fetched_at INTEGER NOT NULL,
data TEXT NOT NULL
);
// Check cache
const cached = await sqlite.execute({
sql: "SELECT data, fetched_at FROM articles WHERE tweet_id = ?",
args: [tweetId]
});
// Is cache valid?
const cacheAge = Date.now() - cached.rows[0].fetched_at;
const isExpired = cacheAge > 24 * 60 * 60 * 1000; // 24 hours
// Store in cache
await sqlite.execute({
sql: "INSERT OR REPLACE INTO articles (tweet_id, fetched_at, data) VALUES (?, ?, ?)",
args: [tweetId, Date.now(), JSON.stringify(apiResponse)]
});
https://api.fxtwitter.com/:username/status/:idhttps://api.fxtwitter.com/francedot/status/2015178880215298557interface APIResponse {
code: number;
message: string;
tweet: {
url: string;
id: string;
author: {
name: string;
screen_name: string;
avatar_url: string;
};
article: {
title: string;
created_at: string;
cover_media: {
url: string;
width: number;
height: number;
};
content: {
blocks: Array<{
type: string; // "unstyled", "header-two", "unordered-list-item", etc.
text: string;
inlineStyleRanges: Array<{
offset: number;
length: number;
style: string; // "BOLD", "ITALIC", etc.
}>;
entityRanges: Array<{
offset: number;
length: number;
key: number;
}>;
}>;
entityMap: {
[key: string]: {
type: string; // "LINK", "CODE", etc.
data: any;
};
};
};
};
};
}
<div class="error-container"> <h1>Unable to load article</h1> <p>[Contextual error message]</p> <a href="[original x.com URL]">View on X.com</a> </div>
x.com/user/status/id → subdomain.val.run/user/status/id/:username/status/:idUser pastes: https://x.com/francedot/status/2015178880215298557
Form extracts: username=francedot, id=2015178880215298557
Redirects to: /francedot/status/2015178880215298557
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>[Article Title] - [Author Name]</title> <!-- OpenGraph tags --> <meta property="og:title" content="[Article Title]"> <meta property="og:description" content="[Preview text or first paragraph]"> <meta property="og:image" content="[Cover media URL]"> <meta property="og:type" content="article"> <!-- Styles --> <link rel="stylesheet" href="[styles.css or inline]"> <!-- Highlight.js for syntax highlighting --> <link rel="stylesheet" href="[highlight.js theme]"> <script src="[highlight.js core + languages]"></script> </head> <body> <article class="article-container"> <img src="[cover_media.url]" alt="Cover image" class="hero-image"> <header class="article-header"> <h1 class="article-title">[Article Title]</h1> <div class="author-info"> <img src="[author.avatar_url]" alt="[author.name]" class="avatar"> <span class="author-name">[author.name]</span> </div> <time class="publish-date">[Relative date]</time> </header> <div class="article-content"> [Rendered blocks with full formatting] </div> <footer class="article-footer"> <a href="[original tweet URL]" target="_blank" rel="noopener"> View original on X.com </a> </footer> </article> </body> </html>
const blockTypeMap = {
'unstyled': 'p',
'header-one': 'h1',
'header-two': 'h2',
'header-three': 'h3',
'unordered-list-item': 'li', // wrap in <ul>
'ordered-list-item': 'li', // wrap in <ol>
'atomic': 'div', // special handling for embedded content
'code-block': 'pre' // wrap in <code> with highlighting
};
function applyInlineStyles(text: string, ranges: InlineStyleRange[]): string {
// Sort ranges by offset
// Apply BOLD → <strong>, ITALIC → <em>, etc.
// Handle overlapping ranges correctly
// Return HTML string with inline formatting
}
function applyEntityRanges(text: string, ranges: EntityRange[], entityMap: EntityMap): string {
// For each entity range:
// - Extract entity from entityMap by key
// - Apply appropriate transformation:
// - LINK → <a href="...">text</a>
// - CODE → <code>text</code>
// - Handle other entity types as needed
// Return HTML string with entities processed
}
<meta property="og:title" content="[Article Title]"> <meta property="og:description" content="[Article preview_text or excerpt]"> <meta property="og:image" content="[cover_media.url]"> <meta property="og:type" content="article"> <meta property="og:url" content="[Current URL]">
<title>[Article Title] by [Author Name]</title>
function formatDate(timestamp: string): string {
const date = new Date(timestamp);
const now = new Date();
const diffInHours = (now - date) / (1000 * 60 * 60);
if (diffInHours < 1) return "Just now";
if (diffInHours < 24) return `${Math.floor(diffInHours)} hours ago`;
if (diffInHours < 48) return "Yesterday";
if (diffInHours < 168) return `${Math.floor(diffInHours / 24)} days ago`;
// For older articles, show full date
return date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
}