10const [query, setQuery] = React.useState("");
11const [targetAccount, setTargetAccount] = React.useState("");
12const [apiKey, setApiKey] = React.useState("");
13const [searchType, setSearchType] = React.useState("Latest");
14const [sinceDate, setSinceDate] = React.useState("");
50const fetchAllTweets = React.useCallback(
51async (fullQuery, type, targetCount) => {
52if (!apiKey.trim()) {
53setError("Please enter your SocialData API Key.");
54return;
55}
79}
8081const response = await fetch(`/api/search?${params.toString()}`, {
82headers: { "X-Api-Key": apiKey },
83});
8488const errorData = await response
89.json()
90.catch(() => ({ message: `API Error: ${response.statusText}` }));
91throw new Error(
92errorData.message
93|| `API Error: ${response.statusText} on page ${pageNum}`,
94);
95}
100if (data.status === "error") {
101throw new Error(
102data.message || `API returned an error on page ${pageNum}`,
103);
104}
135}
136},
137[apiKey],
138);
139144return;
145}
146if (!apiKey.trim()) {
147setError("Please enter your SocialData API Key.");
148return;
149}
176{ className: "app" },
177React.createElement("h1", { className: "title" }, "X Search"),
178// API Key Input
179React.createElement(
180"div",
185React.createElement(
186"label",
187{ htmlFor: "apiKey", className: "label" },
188"API Key",
189),
190React.createElement("input", {
191type: "password",
192id: "apiKey",
193value: apiKey,
194onChange: (e) => setApiKey(e.target.value),
195placeholder: "Enter SocialData API Key",
196className: "input",
197}),
565rel: "noopener noreferrer",
566},
567"SocialData API",
568),
569" | ",
598const url = new URL(request.url);
599600// Search API Proxy Endpoint
601if (url.pathname === "/api/search" && request.method === "GET") {
602const apiKey = request.headers.get("X-Api-Key");
603if (!apiKey) {
604return Response.json(
605{ status: "error", message: "API Key missing in request" },
606{ status: 400 },
607);
619}
620621const apiUrl = new URL("https://api.socialdata.tools/twitter/search");
622apiUrl.searchParams.set("query", query);
623apiUrl.searchParams.set("type", type);
624if (cursor) apiUrl.searchParams.set("cursor", cursor);
625626try {
627const apiResponse = await fetch(apiUrl.toString(), {
628headers: {
629Authorization: `Bearer ${apiKey}`,
630Accept: "application/json",
631},
634let responseData;
635try {
636responseData = await apiResponse.json();
637} catch (e) {
638const text = await apiResponse.text();
639console.error("Non-JSON response from SocialData:", text);
640return Response.json(
641{
642status: "error",
643message: `SocialData API non-JSON response: ${apiResponse.statusText}`,
644},
645{ status: apiResponse.status },
646);
647}
648649if (!apiResponse.ok || responseData.status === "error") {
650console.error(
651`SocialData API Error (${apiResponse.status}):`,
652responseData,
653);
656status: "error",
657message: responseData?.message
658|| `SocialData API Error: ${apiResponse.statusText}`,
659},
660{ status: apiResponse.status },
661);
662}
41: `applications/${DISCORD_APP_ID}/commands`;
4243console.log(`📡 Using API endpoint: ${endpoint}`);
4445try {
4950// Register the command using POST for a single command
51const response = await fetch(`https://discord.com/api/v10/${endpoint}`, {
52method: "POST", // Using POST for single command creation
53headers: {
19console.log(`📤 Sending follow-up message: ${content}`);
20try {
21await fetch(`https://discord.com/api/v10/webhooks/${applicationId}/${interactionToken}/messages/@original`, {
22method: "PATCH",
23headers: {
50: `applications/${DISCORD_APP_ID}/commands`;
5152console.log(`📡 Using API endpoint: ${endpoint}`);
5354try {
5859// Register the command using POST for a single command
60const response = await fetch(`https://discord.com/api/v10/${endpoint}`, {
61method: "POST", // Using POST for single command creation
62headers: {
57: `applications/${DISCORD_APP_ID}/commands`;
5859console.log(`📡 Using API endpoint: ${endpoint}`);
60const results = [];
616768// Register the command using POST for a single command
69const response = await fetch(`https://discord.com/api/v10/${endpoint}`, {
70method: "POST", // Using POST for single command creation
71headers: {
50: `applications/${DISCORD_APP_ID}/commands`;
5152console.log(`📡 Using API endpoint: ${endpoint}`);
5354try {
5859// Register the command using POST for a single command
60const response = await fetch(`https://discord.com/api/v10/${endpoint}`, {
61method: "POST", // Using POST for single command creation
62headers: {
35: `applications/${DISCORD_APP_ID}/commands`;
3637console.log(`📡 Using API endpoint: ${endpoint}`);
3839try {
41console.log("🔍 Checking current commands...");
4243const getResponse = await fetch(`https://discord.com/api/v10/${endpoint}`, {
44method: "GET",
45headers: {
6263// Send empty array to unregister all commands
64const response = await fetch(`https://discord.com/api/v10/${endpoint}`, {
65method: "PUT",
66headers: {
9- **File Browser**: Select specific files to include in the context window for more focused AI assistance
10- **Branch Management**: View, select, and create branches without leaving the app
11- **Cost Tracking**: See estimated API usage costs for each interaction
12- **Sound Notifications**: Get alerted when Claude finishes responding
13- **Mobile-Friendly**: Works on both desktop and mobile devices
14- **Usage Dashboard**: Monitor API usage and inference calls with detailed analytics
1516## How It Works
17181. **Login**: Authenticate with your Val Town API token and Anthropic API key
192. **Select a Project**: Choose which Val Town project you want to work on
203. **Select Files**: Browse your project files and select which ones to include in the context window
26### Prerequisites
2728- A Val Town account with API access
29- An Anthropic API key (Claude 3.7 Sonnet)
3031### Setup
32331. Visit the OpenTownie app
342. Enter your Val Town API token (with `projects:write` and `users:read` permissions)
353. Enter your Anthropic API key
364. Click "Login" to access your projects
3749- React frontend with TypeScript
50- React Router
51- Hono API server backend
52- Web Audio API for sound notifications
53- AI SDK for Claude integration
5455The application proxies requests to the Anthropic API and Val Town API, allowing Claude to view and edit your project files directly.
5657## Privacy & Security
5859- Your Val Town API token and Anthropic API key are stored locally in your browser
60- No data is stored on our servers
61- All communication with the APIs is done directly from your browser
62
Townieschema.tsx2 matches
19finish_reason?: string;
20num_images?: number;
21our_api_token: boolean;
22}
2344finish_reason TEXT,
45num_images INTEGER,
46our_api_token INTEGER NOT NULL,
47finish_timestamp INTEGER
48)
Townieuser-summary.ts1 match
20SUM(num_images) as total_images
21FROM ${USAGE_TABLE}
22WHERE our_api_token = 1
23GROUP BY user_id, username
24ORDER BY total_price DESC