惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

A
About on SuperTechFans
G
Google Developers Blog
L
LangChain Blog
aimingoo的专栏
aimingoo的专栏
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
云风的 BLOG
云风的 BLOG
小众软件
小众软件
月光博客
月光博客
Recent Announcements
Recent Announcements
人人都是产品经理
人人都是产品经理
P
Proofpoint News Feed
博客园 - 聂微东
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
雷峰网
雷峰网
The Cloudflare Blog
博客园_首页
美团技术团队
大猫的无限游戏
大猫的无限游戏
B
Blog
IT之家
IT之家
Jina AI
Jina AI
H
Hackread – Cybersecurity News, Data Breaches, AI and More
C
Check Point Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Building a Weather Widget for Firefox New Tab: API-Free A...
Weather Cloc · 2026-05-04 · via DEV Community

Building a Weather Widget for Firefox New Tab: API-Free Approach with wttr.in

Most weather widgets require API keys, rate limits, and sometimes credit cards. There's a better way: wttr.in — a free, open, consent-friendly weather service that requires zero authentication.

What is wttr.in?

wttr.in is a console-oriented weather service by Igor Chubin. It supports:

  • Plain text output (for terminals)
  • JSON API output (for apps)
  • Auto-detects location from IP
  • Supports city name, coordinates, airport codes, and more

The JSON API

https://wttr.in/London?format=j1

Enter fullscreen mode Exit fullscreen mode

This returns a full JSON response with current conditions plus a 3-day forecast. No API key. No account. No rate limit (be reasonable).

Parsing the Response

async function fetchWeather(location = '') {
  const url = `https://wttr.in/${encodeURIComponent(location)}?format=j1`;

  const response = await fetch(url);
  if (!response.ok) throw new Error(`Weather fetch failed: ${response.status}`);

  const data = await response.json();
  return parseWeatherData(data);
}

function parseWeatherData(data) {
  const current = data.current_condition[0];
  const today = data.weather[0];
  const tomorrow = data.weather[1];
  const dayAfter = data.weather[2];

  return {
    temperature: {
      c: parseInt(current.temp_C),
      f: parseInt(current.temp_F),
    },
    feelsLike: {
      c: parseInt(current.FeelsLikeC),
      f: parseInt(current.FeelsLikeF),
    },
    humidity: parseInt(current.humidity),
    description: current.weatherDesc[0].value,
    weatherCode: parseInt(current.weatherCode),
    forecast: [
      parseForecastDay(today),
      parseForecastDay(tomorrow),
      parseForecastDay(dayAfter),
    ],
    location: data.nearest_area[0],
  };
}

function parseForecastDay(day) {
  return {
    date: day.date,
    maxC: parseInt(day.maxtempC),
    minC: parseInt(day.mintempC),
    maxF: parseInt(day.maxtempF),
    minF: parseInt(day.mintempF),
    description: day.hourly[4]?.weatherDesc[0]?.value || '',
    weatherCode: parseInt(day.hourly[4]?.weatherCode || 0),
    sunrise: day.astronomy[0]?.sunrise || '',
    sunset: day.astronomy[0]?.sunset || '',
  };
}

Enter fullscreen mode Exit fullscreen mode

Weather Code → Icon Mapping

wttr.in uses standard WMO weather interpretation codes:

function getWeatherIcon(code) {
  const icons = {
    113: '☀️',  // Sunny/Clear
    116: '',  // Partly cloudy
    119: '☁️',  // Cloudy
    122: '☁️',  // Overcast
    143: '🌫️', // Mist
    176: '🌦️', // Patchy rain
    179: '🌨️', // Patchy snow
    182: '🌧️', // Patchy sleet
    185: '🌧️', // Patchy freezing drizzle
    200: '⛈️', // Thundery outbreaks
    227: '🌨️', // Blowing snow
    230: '❄️',  // Blizzard
    248: '🌫️', // Fog
    260: '🌫️', // Freezing fog
    263: '🌦️', // Light drizzle
    266: '🌧️', // Drizzle
    281: '🌧️', // Freezing drizzle
    284: '🌧️', // Heavy freezing drizzle
    293: '🌦️', // Light rain
    296: '🌧️', // Rain
    299: '🌧️', // Moderate rain
    302: '🌧️', // Heavy rain
    305: '🌧️', // Heavy rain at times
    308: '🌧️', // Very heavy rain
    311: '🌧️', // Light sleet
    314: '🌧️', // Sleet
    317: '🌨️', // Light snow/sleet
    320: '🌨️', // Moderate or heavy sleet
    323: '🌨️', // Light snow
    326: '🌨️', // Snow
    329: '❄️',  // Moderate snow
    332: '❄️',  // Heavy snow
    335: '❄️',  // Snow blowing
    338: '❄️',  // Heavy snow
    350: '🌧️', // Ice pellets
    353: '🌦️', // Light rain shower
    356: '🌧️', // Moderate or heavy rain shower
    359: '🌧️', // Torrential rain shower
    362: '🌨️', // Light sleet showers
    365: '🌨️', // Moderate or heavy sleet showers
    368: '🌨️', // Light snow showers
    371: '🌨️', // Moderate or heavy snow showers
    374: '🌨️', // Light showers of ice pellets
    377: '🌨️', // Moderate or heavy showers of ice pellets
    386: '⛈️', // Patchy light rain with thunder
    389: '⛈️', // Moderate or heavy rain with thunder
    392: '⛈️', // Patchy light snow with thunder
    395: '⛈️', // Moderate or heavy snow with thunder
  };
  return icons[code] || '🌡️';
}

Enter fullscreen mode Exit fullscreen mode

Auto-Location Detection

wttr.in detects location from the request IP by default — just omit the location parameter:

// Auto-detect location
const weather = await fetchWeather('');
// or
const weather = await fetchWeather('auto');

Enter fullscreen mode Exit fullscreen mode

For browser extensions, this works great as the fetch goes from the user's IP.

Caching Strategy

Weather doesn't change every second. Cache aggressively:

const CACHE_DURATION_MS = 10 * 60 * 1000; // 10 minutes

async function fetchWeatherCached(location = '') {
  const cacheKey = `weather_${location}`;
  const cached = await browser.storage.local.get(cacheKey);

  if (cached[cacheKey]) {
    const { data, timestamp } = cached[cacheKey];
    if (Date.now() - timestamp < CACHE_DURATION_MS) {
      return data; // Return cached data
    }
  }

  // Fetch fresh data
  const data = await fetchWeather(location);
  await browser.storage.local.set({
    [cacheKey]: { data, timestamp: Date.now() }
  });
  return data;
}

Enter fullscreen mode Exit fullscreen mode

Error Handling

async function fetchWeatherSafe(location = '') {
  try {
    return await fetchWeatherCached(location);
  } catch (error) {
    console.warn('Weather fetch failed:', error);
    // Try to return stale cache rather than showing error
    const cacheKey = `weather_${location}`;
    const cached = await browser.storage.local.get(cacheKey);
    if (cached[cacheKey]) {
      return { ...cached[cacheKey].data, stale: true };
    }
    return null;
  }
}

Enter fullscreen mode Exit fullscreen mode

Why Not OpenWeatherMap?

wttr.in OpenWeatherMap Free Tier
API Key Not required Required
Rate limit Generous (no hard limit) 60 calls/min, 1M/month
Setup friction Zero Account + key management
Privacy IP-based location Account linked
Self-hostable Yes (open source) No

For a privacy-focused extension, wttr.in is a natural fit — no account, no tracking, no API key to manage or expire.

The Result

This approach powers the weather widget in Weather & Clock Dashboard — a free Firefox new tab extension. Zero API keys for users to configure, works immediately after install, and respects privacy.


Weather & Clock Dashboard — live weather + 3-day forecast, world clocks, search bar for your Firefox new tab. Free, no tracking, MIT licensed.