birdfactsWikipediaAPI.ts9 matches
6}
78export class WikipediaAPI {
9private baseURL = "https://en.wikipedia.org/api/rest_v1";
1011async getBirdInfo(birdName: string): Promise<WikipediaResult | null> {
18headers: {
19"Accept": "application/json",
20"User-Agent": "BirdFactsAPI/1.0 (https://val.town)"
21}
22});
27return await this.searchAndGetInfo(birdName);
28}
29throw new Error(`Wikipedia API error: ${response.status} ${response.statusText}`);
30}
3149return result;
50} catch (error) {
51console.error("Wikipedia API error:", error);
52return null; // Graceful fallback
53}
56private async searchAndGetInfo(birdName: string): Promise<WikipediaResult | null> {
57try {
58// Use Wikipedia's search API to find the best match
59const searchUrl = `https://en.wikipedia.org/w/api.php?action=query&format=json&list=search&srsearch=${encodeURIComponent(birdName + " bird")}&srlimit=1&origin=*`;
60
61const searchResponse = await fetch(searchUrl);
96if (match && match[1]) {
97const candidate = match[1];
98// Verify it looks like a scientific name (two words, first capitalized)
99const parts = candidate.split(' ');
100if (parts.length === 2 &&
113try {
114const pageTitle = this.formatPageTitle(birdName);
115const url = `https://en.wikipedia.org/w/api.php?action=query&format=json&prop=extracts|pageimages&exintro=true&explaintext=true&piprop=original&titles=${encodeURIComponent(pageTitle)}&origin=*`;
116
117const response = await fetch(url);
birdfactsEBirdAPI.ts16 matches
7}
89export class EBirdAPI {
10private baseURL = "https://api.ebird.org/v2";
11private apiKey: string | undefined;
1213constructor() {
14this.apiKey = Deno.env.get("EBIRD_API_KEY");
15}
1620};
2122if (this.apiKey) {
23headers["X-eBirdApiToken"] = this.apiKey;
24}
2536if (!response.ok) {
37if (response.status === 429) {
38throw new Error("eBird API rate limit exceeded");
39}
40throw new Error(`eBird API error: ${response.status} ${response.statusText}`);
41}
4244return data as EBirdObservation[];
45} catch (error) {
46console.error("eBird API error:", error);
47throw error;
48}
58if (!response.ok) {
59if (response.status === 429) {
60throw new Error("eBird API rate limit exceeded");
61}
62throw new Error(`eBird API error: ${response.status} ${response.statusText}`);
63}
6466return data as EBirdObservation[];
67} catch (error) {
68console.error("eBird API error:", error);
69throw error;
70}
7980if (!response.ok) {
81throw new Error(`eBird API error: ${response.status} ${response.statusText}`);
82}
8385return data;
86} catch (error) {
87console.error("eBird hotspots API error:", error);
88throw error;
89}
99100if (!response.ok) {
101throw new Error(`eBird API error: ${response.status} ${response.statusText}`);
102}
103105return data as EBirdObservation[];
106} catch (error) {
107console.error("eBird notable observations API error:", error);
108throw error;
109}
birdfactsBirdFactsService.ts15 matches
1import { blob } from "https://esm.town/v/std/blob";
2import { EBirdAPI } from "./EBirdAPI.ts";
3import { APINinjasAPI } from "./APINinjasAPI.ts";
4import { WikipediaAPI } from "./WikipediaAPI.ts";
56export interface BirdFactRequest {
3233export class BirdFactsService {
34private eBirdAPI: EBirdAPI;
35private apiNinjasAPI: APINinjasAPI;
36private wikipediaAPI: WikipediaAPI;
37private cachePrefix = "bird_facts_";
38private regionalCachePrefix = "regional_birds_";
4041constructor() {
42this.eBirdAPI = new EBirdAPI();
43this.apiNinjasAPI = new APINinjasAPI();
44this.wikipediaAPI = new WikipediaAPI();
45}
4654} else if (request.lat && request.lng) {
55// Get region from coordinates and then birds
56const nearbyBirds = await this.eBirdAPI.getNearbyBirds(request.lat, request.lng);
57birds = nearbyBirds;
58region = `${request.lat.toFixed(2)},${request.lng.toFixed(2)}`;
9293try {
94const birds = await this.eBirdAPI.getRegionalBirds(regionCode);
95
96// Cache the results
158// Fetch data from multiple sources concurrently
159const [ninjasFact, wikipediaData] = await Promise.allSettled([
160this.apiNinjasAPI.getBirdFact(bird.comName),
161this.wikipediaAPI.getBirdInfo(bird.comName)
162]);
163164// Add API Ninjas fact if available
165if (ninjasFact.status === 'fulfilled' && ninjasFact.value) {
166result.fact = ninjasFact.value;
176}
177178// If we don't have a fact from API Ninjas, try to extract one from Wikipedia
179if (!result.fact && result.wikipedia?.summary) {
180result.fact = this.extractFactFromWikipedia(result.wikipedia.summary);
157<script>
158(function() {
159const apiUrl = '${sourceUrl}api';
160const app = document.getElementById('app-container');
161const nav = document.getElementById('main-nav');
162let state = { token: localStorage.getItem('strive_token') };
163164// --- API CLIENT ---
165async function resilientFetch(path, options = {}) {
166options.headers = { ...options.headers, 'Content-Type': 'application/json' };
167if (state.token) options.headers['Authorization'] = \`Bearer \${state.token}\`;
168
169const response = await fetch(\`\${apiUrl}\${path}\`, options);
170if (!response.ok) {
171const err = await response.json();
172throw new Error(err.error || 'API request failed');
173}
174return response.status === 204 ? null : response.json();
175}
176177const api = {
178login: (username, secret_phrase) => resilientFetch('/login', { method: 'POST', body: JSON.stringify({ username, secret_phrase }) }),
179register: (username, secret_phrase) => resilientFetch('/register', { method: 'POST', body: JSON.stringify({ username, secret_phrase }) }),
180getFeed: () => resilientFetch('/feed'),
181// Add all other api methods here...
182};
183254}
255256// --- API ROUTING ---
257if (path.startsWith("/api/")) {
258try {
259const userId = await verifyAuth(req);
260261// --- Unprotected Routes ---
262if (path === "/api/register" && req.method === "POST") {
263const { username, secret_phrase } = body as any;
264if (!username || !secret_phrase) return jsonResponse({ error: "Username and secret phrase required" }, 400);
270return jsonResponse({ message: "User registered" }, 201);
271}
272if (path === "/api/login" && req.method === "POST") {
273const { username, secret_phrase } = body as any;
274const hashed = await hashToken(secret_phrase);
288289// --- Habit CRUD ---
290if (path === "/api/habits" && req.method === "POST") {
291const { name, is_public } = body as any;
292const id = crypto.randomUUID();
299}
300301const habitMatch = path.match(/^\/api\/habits\/([a-zA-Z0-9\-]+)$/);
302if (habitMatch && req.method === "DELETE") {
303const habitId = habitMatch[1];
307308// --- AI Routes ---
309const aiTipsMatch = path.match(/^\/api\/habits\/(?<habitId>[a-zA-Z0-9\-]+)\/ai-tips$/);
310if (aiTipsMatch && req.method === "POST") {
311const { habitId } = aiTipsMatch.groups;
334}
335336const aiAffirmationsMatch = path.match(/^\/api\/habits\/(?<habitId>[a-zA-Z0-9\-]+)\/ai-affirmations$/);
337if (aiAffirmationsMatch && req.method === "POST") {
338const { habitId } = aiAffirmationsMatch.groups;
365366// --- Feed ---
367if (path === "/api/feed" && req.method === "GET") {
368const { rows } = await sqlite.execute({
369sql: `
380381// All other routes...
382return jsonResponse({ error: "API route not found or method not allowed." }, 404);
383} catch (e) {
384console.error(e);
134<meta name="viewport" content="width=device-width, initial-scale=1.0">
135<title>The Reflective Chamber</title>
136<link rel="preconnect" href="https://fonts.googleapis.com">
137<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
138<link href="https://fonts.googleapis.com/css2?family=Lora:ital,wght@0,400..700;1,400..700&family=Source+Sans+3:wght@300;400;600&display=swap" rel="stylesheet">
139<style>
140:root {
340<script>
341(function() {
342const API_URL = '${sourceUrl}';
343const affirmations = ${JSON.stringify(affirmations)};
344419420try {
421const guidance = await resilientFetch(\`\${API_URL}api/reframe\`, {
422method: 'POST',
423headers: { 'Content-Type': 'application/json' },
439}
440441// --- API & ROUTING LOGIC ---
442443const app = new Hono();
450});
451452// Middleware for CORS, allowing the frontend to call the API.
453app.use(
454"/api/*",
455cors({
456origin: "*", // In a real application, restrict this to the specific val's domain.
479}
480481// API endpoint to analyze an entry and provide guidance.
482app.post("/api/reframe", async (c) => {
483const { entry_text } = await c.req.json();
484const openai = new OpenAI();
546const url = new URL(req.url);
547548// Route API requests to Hono.
549if (url.pathname.startsWith("/api/")) {
550return app.fetch(req);
551}
myNewWebsiteindex.ts2 matches
12app.get("/frontend/**/*", c => serveFile(c.req.path, import.meta.url));
1314// Add your API routes here
15// app.get("/api/data", c => c.json({ hello: "world" }));
1617// Unwrap and rethrow Hono errors as the original error
stevensDemosendDailyBrief.ts8 matches
9798export async function sendDailyBriefing(chatId?: string, today?: DateTime) {
99// Get API keys from environment
100const apiKey = Deno.env.get("ANTHROPIC_API_KEY");
101const telegramToken = Deno.env.get("TELEGRAM_TOKEN");
102106}
107108if (!apiKey) {
109console.error("Anthropic API key is not configured.");
110return;
111}
122123// Initialize Anthropic client
124const anthropic = new Anthropic({ apiKey });
125126// Initialize Telegram bot
162163// disabled title for now, it seemes unnecessary...
164// await bot.api.sendMessage(chatId, `*${title}*`, { parse_mode: "Markdown" });
165166// Then send the main content
169170if (content.length <= MAX_LENGTH) {
171await bot.api.sendMessage(chatId, content, { parse_mode: "Markdown" });
172// Store the briefing in chat history
173await storeChatMessage(
198// Send each chunk as a separate message and store in chat history
199for (const chunk of chunks) {
200await bot.api.sendMessage(chatId, chunk, { parse_mode: "Markdown" });
201// Store each chunk in chat history
202await storeChatMessage(
stevensDemoREADME.md1 match
53You'll need to set up some environment variables to make it run.
5455- `ANTHROPIC_API_KEY` for LLM calls
56- You'll need to follow [these instructions](https://docs.val.town/integrations/telegram/) to make a telegram bot, and set `TELEGRAM_TOKEN`. You'll also need to get a `TELEGRAM_CHAT_ID` in order to have the bot remember chat contents.
57- For the Google Calendar integration you'll need `GOOGLE_CALENDAR_ACCOUNT_ID` and `GOOGLE_CALENDAR_CALENDAR_ID`. See [these instuctions](https://www.val.town/v/stevekrouse/pipedream) for details.
stevensDemoREADME.md5 matches
8## Hono
910This app uses [Hono](https://hono.dev/) as the API framework. You can think of Hono as a replacement for [ExpressJS](https://expressjs.com/) that works in serverless environments like Val Town or Cloudflare Workers. If you come from Python or Ruby, Hono is also a lot like [Flask](https://github.com/pallets/flask) or [Sinatra](https://github.com/sinatra/sinatra), respectively.
1112## Serving assets to the frontend
20### `index.html`
2122The most complicated part of this backend API is serving index.html. In this app (like most apps) we serve it at the root, ie `GET /`.
2324We *bootstrap* `index.html` with some initial data from the server, so that it gets dynamically injected JSON data without having to make another round-trip request to the server to get that data on the frontend. This is a common pattern for client-side rendered apps.
2526## CRUD API Routes
2728This app has two CRUD API routes: for reading and inserting into the messages table. They both speak JSON, which is standard. They import their functions from `/backend/database/queries.ts`. These routes are called from the React app to refresh and update data.
2930## Errors
3132Hono and other API frameworks have a habit of swallowing up Errors. We turn off this default behavior by re-throwing errors, because we think most of the time you'll want to see the full stack trace instead of merely "Internal Server Error". You can customize how you want errors to appear.
stevensDemoNotebookView.tsx5 matches
8import { type Memory } from "../../shared/types.ts";
910const API_BASE = "/api/memories";
11const MEMORIES_PER_PAGE = 20;
1271setError(null);
72try {
73const response = await fetch(API_BASE);
74if (!response.ok) {
75throw new Error(`HTTP error! status: ${response.status}`);
100101try {
102const response = await fetch(API_BASE, {
103method: "POST",
104headers: { "Content-Type": "application/json" },
123124try {
125const response = await fetch(`${API_BASE}/${id}`, {
126method: "DELETE",
127});
155156try {
157const response = await fetch(`${API_BASE}/${editingMemory.id}`, {
158method: "PUT",
159headers: { "Content-Type": "application/json" },