
Newsapi Search
- 32 installs
- 61 repo stars
- Updated March 16, 2026
- kirkluokun/awesome-a-stock-openclawskills
Helps with backend & apis tasks.
About
newsapi-search is a Claude Code skill for backend & apis. It helps solo builders move faster with AI-assisted development.
- newsapi-search
- Backend & APIs
- AI-coding skill
Newsapi Search by the numbers
- 32 all-time installs (skills.sh)
- Ranked #3,359 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/kirkluokun/awesome-a-stock-openclawskills --skill newsapi-searchAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 32 |
|---|---|
| repo stars | ★ 61 |
| Last updated | March 16, 2026 |
| Repository | kirkluokun/awesome-a-stock-openclawskills ↗ |
What it does
Helps with backend & apis tasks.
Files
NewsAPI Search
Search 5,000+ news sources via NewsAPI. Supports comprehensive article discovery (/everything) and breaking headlines (/top-headlines).
Quick Start
# Basic search
node scripts/search.js "technology" --days 7
# Filter by quality sources
node scripts/search.js "technology" --sources bbc-news,reuters,al-jazeera-english
# Exclude low-quality domains
node scripts/search.js "technology" --exclude tmz.com,radaronline.com
# Breaking headlines
node scripts/search.js "technology" --headlines --country us
# List available sources
node scripts/sources.js --country us --category generalSetup
Add API key to ~/.openclaw/.env:
NEWSAPI_KEY=your_api_keyGet key from https://newsapi.org (free tier: 100 requests/day)
Endpoints
Everything Search
Comprehensive search across millions of articles.
Time Windows:
node scripts/search.js "query" --hours 24
node scripts/search.js "query" --days 7 # default
node scripts/search.js "query" --weeks 2
node scripts/search.js "query" --months 1
node scripts/search.js "query" --from 2026-01-01 --to 2026-01-31Filters:
node scripts/search.js "query" --sources bbc-news,cnn # max 20
node scripts/search.js "query" --domains nytimes.com,bbc.co.uk
node scripts/search.js "query" --exclude gossip-site.com
node scripts/search.js "query" --lang en # or 'any'Search Fields:
node scripts/search.js "query" --title-only # title only
node scripts/search.js "query" --in title,description # specific fieldsAdvanced Query Syntax:
"exact phrase"— exact match+musthave— required word-exclude— excluded wordword1 AND word2— both requiredword1 OR word2— either accepted(word1 OR word2) AND word3— grouping
Pagination & Sorting:
node scripts/search.js "query" --page 2 --limit 20
node scripts/search.js "query" --sort relevancy # default
node scripts/search.js "query" --sort date # newest first
node scripts/search.js "query" --sort popularityTop Headlines
Live breaking news by country or category.
# By country
node scripts/search.js "query" --headlines --country us
# By category
node scripts/search.js --headlines --country us --category business
# By source
node scripts/search.js --headlines --sources bbc-news,cnnCategories: business, entertainment, general, health, science, sports, technology
Note: Cannot mix --country/--category with --sources in headlines mode.
List Sources
node scripts/sources.js # all sources
node scripts/sources.js --country us # filter by country
node scripts/sources.js --category business
node scripts/sources.js --lang en
node scripts/sources.js --json # JSON outputAdvanced Usage
For complete parameter reference, see references/api-reference.md.
For common workflows and search patterns, see references/examples.md.
Programmatic API
const { searchEverything, searchHeadlines, getSources } = require('./scripts/search.js');
const results = await searchEverything('climate change', {
timeWindow: { type: 'days', value: 7 },
sources: 'bbc-news,reuters',
excludeDomains: 'tmz.com',
limit: 20
});
const headlines = await searchHeadlines('business', {
country: 'us',
category: 'business'
});Free Tier Limits
- 100 requests/day
- 100 results per request (max)
- 1-month delay on archived content
Output Format
Returns structured JSON:
{
"query": "technology",
"endpoint": "everything",
"totalResults": 64,
"returnedResults": 10,
"page": 1,
"results": [
{
"title": "...",
"url": "...",
"source": "BBC News",
"publishedAt": "2026-02-05T14:30:00Z",
"description": "...",
"content": "..."
}
]
}{
"owner": "hegghammer",
"slug": "newsapi-search",
"displayName": "NewsAPI Search",
"latest": {
"version": "1.0.0",
"publishedAt": 1770329770019,
"commit": "https://github.com/clawdbot/skills/commit/ca2f4292d5b32881b1f56a26276ff14c9e97a5ef"
},
"history": []
}
# NewsAPI 密钥(用于新闻聚合搜索)
# 获取地址:https://newsapi.org/register(免费套餐每日 100 次请求)
NEWSAPI_KEY=your_newsapi_key_here
NewsAPI Parameter Reference
Complete reference for all NewsAPI endpoints and parameters.
Table of Contents
- Everything Endpoint
- Top Headlines Endpoint
- Sources Endpoint
- Query Syntax
- Language Codes
- Country Codes
- Category Codes
---
Everything Endpoint
GET https://newsapi.org/v2/everything
Search through millions of articles from 5,000+ sources.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
q | string | No* | Keywords or phrases (max 500 chars). Supports advanced search. Required unless using qInTitle. |
qInTitle | string | No* | Keywords/phrases to search in article titles only. |
sources | string | No | Comma-separated source IDs (max 20). Cannot mix with domains. |
domains | string | No | Comma-separated domains to restrict search (e.g., bbc.co.uk,nytimes.com). |
excludeDomains | string | No | Comma-separated domains to exclude. |
from | string | No | Start date (ISO 8601: 2026-02-05 or 2026-02-05T21:13:17). |
to | string | No | End date (ISO 8601). |
language | string | No | 2-letter ISO-639-1 code. Default: all languages. |
sortBy | string | No | relevancy (default), publishedAt, popularity. |
pageSize | int | No | Results per page. Default: 100. Max: 100. |
page | int | No | Page number. Default: 1. |
searchIn | string | No | Fields to search: title, description, content (comma-separated). |
apiKey | string | Yes | Your API key. |
CLI Mapping
| CLI Flag | API Parameter |
|---|---|
--title-only | Uses qInTitle instead of q |
--sources | sources |
--domains | domains |
--exclude | excludeDomains |
--from | from |
--to | to |
--lang | language |
--sort | sortBy (date maps to publishedAt) |
--limit | pageSize |
--page | page |
--in | searchIn |
---
Top Headlines Endpoint
GET https://newsapi.org/v2/top-headlines
Live breaking headlines for a country, category, or source.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
country | string | No | 2-letter ISO 3166-1 code. Cannot mix with `sources`. |
category | string | No | Category. Cannot mix with `sources`. |
sources | string | No | Comma-separated source IDs. Cannot mix with `country` or `category`. |
q | string | No | Keywords to search in headlines. |
pageSize | int | No | Results per page. Default: 20. Max: 100. |
page | int | No | Page number. Default: 1. |
apiKey | string | Yes | Your API key. |
CLI Mapping
| CLI Flag | API Parameter |
|---|---|
--headlines | Switches to top-headlines endpoint |
--country | country |
--category | category |
--sources | sources |
--limit | pageSize |
--page | page |
---
Sources Endpoint
GET https://newsapi.org/v2/top-headlines/sources
List available news sources.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
country | string | No | Filter by 2-letter country code. |
category | string | No | Filter by category. |
language | string | No | Filter by 2-letter language code. |
apiKey | string | Yes | Your API key. |
Response Fields
| Field | Description |
|---|---|
id | Source identifier (use with --sources). |
name | Display name. |
description | Source description. |
url | Homepage URL. |
category | News category. |
language | 2-letter language code. |
country | 2-letter country code. |
---
Query Syntax
NewsAPI supports Google's search syntax for the q parameter.
Operators
| Syntax | Meaning | Example |
|---|---|---|
"phrase" | Exact match | "police manhunt" |
+word | Must include | +manhunt +fugitive |
-word | Must exclude | manhunt -gaming |
AND | Both required | police AND manhunt |
OR | Either acceptable | manhunt OR hunt |
NOT | Exclude | manhunt NOT game |
() | Grouping | (police OR federal) AND manhunt |
Examples
# Exact phrase in title
node scripts/search.js '"police manhunt"' --title-only
# Must include police, exclude gaming
node scripts/search.js '+police +manhunt -gaming'
# Terrorist OR fugitive, plus manhunt
node scripts/search.js '(terrorist OR fugitive) AND manhunt'---
Language Codes
| Code | Language |
|---|---|
ar | Arabic |
de | German |
en | English |
es | Spanish |
fr | French |
he | Hebrew |
it | Italian |
nl | Dutch |
no | Norwegian |
pt | Portuguese |
ru | Russian |
sv | Swedish |
ud | Urdu |
zh | Chinese |
Use --lang any to disable language filtering.
---
Country Codes
Common country codes for headlines/sources:
| Code | Country |
|---|---|
au | Australia |
br | Brazil |
ca | Canada |
cn | China |
de | Germany |
fr | France |
gb | United Kingdom |
in | India |
it | Italy |
jp | Japan |
ru | Russia |
us | United States |
Full list: https://newsapi.org/sources
---
Category Codes
Available for headlines and source filtering:
| Code | Description |
|---|---|
business | Business news |
entertainment | Entertainment/gossip |
general | General news |
health | Health & medicine |
science | Science & technology |
sports | Sports |
technology | Technology |
---
Sort Options
| Option | Description |
|---|---|
relevancy | Articles most relevant to query first. |
publishedAt | Newest articles first. |
popularity | Articles from popular sources first. |
CLI shortcuts: --sort relevancy, --sort date (maps to publishedAt), --sort popularity.
---
Error Codes
| Code | Meaning | Resolution |
|---|---|---|
apiKeyMissing | No API key provided | Add NEWSAPI_KEY to .env |
apiKeyInvalid | Invalid API key | Verify key at newsapi.org |
rateLimited | Too many requests | Wait or upgrade plan |
parametersMissing | Required params missing | Check query/topic provided |
parametersInvalid | Invalid parameter value | Check date formats, codes |
sourceDoesNotExist | Source ID not found | Run sources.js to list valid IDs |
NewsAPI Search Examples
Common workflows and search patterns for research and monitoring.
Table of Contents
---
Research Workflows
Track a Topic Over Time
Paginate through results to build a comprehensive dataset:
# Get first page
node scripts/search.js "climate summit" --weeks 2 --limit 100 --page 1 > page1.json
# Get second page
node scripts/search.js "climate summit" --weeks 2 --limit 100 --page 2 > page2.json
# Sort by date for chronological analysis
node scripts/search.js "climate summit" --weeks 2 --sort date --limit 100Monitor Breaking Developments
Check last 24 hours for new stories:
node scripts/search.js "product launch" --hours 24 --sort dateHistorical Analysis
Search specific date ranges:
# Specific event period
node scripts/search.js "olympics" --from 2024-07-26 --to 2024-08-11
# Year-long study
node scripts/search.js "electric vehicles" --from 2025-01-01 --to 2025-12-31 --limit 100---
Source Filtering
Quality News Only
Filter to major reputable outlets:
node scripts/search.js "technology" --days 7 \
--sources bbc-news,reuters,associated-press,al-jazeera-english,the-guardian-ukRegional Focus
UK sources only:
# Method 1: Source IDs
node scripts/search.js "business" --sources bbc-news,the-guardian-uk,daily-mail
# Method 2: Domain filtering
node scripts/search.js "business" --domains bbc.co.uk,theguardian.com
# Method 3: List UK sources first
node scripts/sources.js --country gbExclude Low-Quality Sources
Remove tabloids, gossip, and aggregators:
node scripts/search.js "finance" --days 7 \
--exclude tmz.com,radaronline.com,dailystar.co.ukAcademic/Policy Sources
Search think tanks and policy publications:
node scripts/search.js "economics" --domains brookings.edu,cfr.org,foreignpolicy.com---
Time-Based Searches
Recency Levels
# Breaking (last hour)
node scripts/search.js "superbowl" --hours 1
# Recent (today)
node scripts/search.js "superbowl" --hours 24
# This week
node scripts/search.js "superbowl" --days 7
# This month
node scripts/search.js "superbowl" --weeks 4
# Quarter
node scripts/search.js "superbowl" --months 3Date Ranges for Studies
# Specific event period
node scripts/search.js "world cup" --from 2022-11-20 --to 2022-12-18
# Annual comparison
node scripts/search.js "electric vehicles" --from 2024-01-01 --to 2024-12-31
# Pre/post event analysis
node scripts/search.js "stock market" --from 2025-06-01 --to 2025-07-01---
Headlines Monitoring
Current Breaking News
# US breaking news
node scripts/search.js --headlines --country us --limit 20
# With keyword filter
node scripts/search.js "elon musk" --headlines --country us
# By category
node scripts/search.js --headlines --country us --category business
node scripts/search.js --headlines --country gb --category technologyInternational Headlines
node scripts/search.js --headlines --country de # Germany
node scripts/search.js --headlines --country fr # France
node scripts/search.js --headlines --country au # AustraliaSource-Specific Headlines
# BBC headlines only
node scripts/search.js --headlines --sources bbc-news
# Multiple sources
node scripts/search.js --headlines --sources bbc-news,cnn,reuters---
Advanced Query Patterns
Precise Term Matching
# Exact phrase in title (high precision)
node scripts/search.js '"climate change"' --title-only
# Multiple required terms
node scripts/search.js '+apple +iphone +review' --days 7
# Phrase with exclusions
node scripts/search.js '"superbowl halftime" -commercial -ads' --days 7Concept Expansion
# Synonym expansion with OR
node scripts/search.js '(electric OR ev OR battery) AND (car OR vehicle)' --days 7
# Different actor types
node scripts/search.js '(government OR federal OR state) AND policy' --days 7
# Geographic variations
node scripts/search.js 'summit AND (London OR Paris OR Berlin OR Madrid)' --weeks 2Boolean Logic
# Complex grouping
node scripts/search.js '(sports OR athletics OR competition) AND (olympics OR world cup) NOT (video OR game)'
# Multiple conditions
node scripts/search.js '(+tesla +elon) OR (+spacex +rocket)' --days 7---
Language & Region Patterns
Non-English Sources
# Spanish language
node scripts/search.js "fútbol" --lang es --days 7
# German language
node scripts/search.js "bundesliga" --lang de --days 7
# French language
node scripts/search.js "coupe du monde" --lang fr --days 7
# All languages (no filter)
node scripts/search.js "climate" --lang any --days 7Regional by Domain
# UK press
node scripts/search.js "business" --domains bbc.co.uk,theguardian.com,dailymail.co.uk
# US press
node scripts/search.js "business" --domains nytimes.com,cnn.com,foxnews.com
# International mix
node scripts/search.js "business" --domains bbc.co.uk,aljazeera.com,reuters.com---
Sorting Strategies
By Relevance (Default)
Best for focused research on specific topics:
node scripts/search.js '"artificial intelligence"' --sort relevancy --limit 20By Date
Best for tracking chronology:
# Newest first (breaking news)
node scripts/search.js "product launch" --sort date --hours 24
# For historical timeline analysis
node scripts/search.js "product launch" --sort date --weeks 4By Popularity
Best for finding "most discussed" stories:
node scripts/search.js "celebrity news" --sort popularity --days 7---
Discovery Workflows
Find Related Sources
# 1. Search broadly
node scripts/search.js "technology" --days 1 --limit 100 > results.json
# 2. Extract unique sources from results
# (Use jq: cat results.json | jq -r '.results[].source' | sort | uniq)
# 3. Get source IDs
node scripts/sources.js --country us --category general
# 4. Re-search with specific sources
node scripts/search.js "technology" --sources source-id-1,source-id-2Source Quality Assessment
# List sources in a country
node scripts/sources.js --country us
# Test each major source
node scripts/search.js "test" --sources bbc-news --limit 1
node scripts/search.js "test" --sources cnn --limit 1---
Research Output Workflows
Save Results for Analysis
# JSON for programmatic analysis
node scripts/search.js "electric vehicles" --weeks 2 --limit 100 > ev_articles.json
# View with jq
# cat ev_articles.json | jq '.results[] | {title, source, date: .publishedAt}'
# Extract URLs for archiving
# cat ev_articles.json | jq -r '.results[].url'Comparative Searches
# Compare coverage of different terms
node scripts/search.js "iphone" --weeks 1 > term1.json
node scripts/search.js "android" --weeks 1 > term2.json
# Compare counts
# jq '.totalResults' term1.json term2.json---
Common Research Scenarios
Scenario: Market Research
# Comprehensive search across quality sources
node scripts/search.js "product launch" --months 6 \
--sources bbc-news,reuters,associated-press,techcrunch,the-verge \
--sort date \
--limit 100Scenario: Media Monitoring
# Daily check for new stories
node scripts/search.js "company OR brand" --hours 24 --sort date
# Exclude entertainment/gaming false positives
node scripts/search.js "apple" --hours 24 --exclude polygon.com,kotaku.com,ign.comScenario: Event Tracking
# During active event (every few hours)
node scripts/search.js "superbowl 2026" --hours 6 --sort date
# After event (daily digest)
node scripts/search.js "superbowl recap" --hours 24 --sort relevancyScenario: Comparative Regional Study
# Same topic, different countries
node scripts/search.js "electric vehicles" --headlines --country us > us_headlines.json
node scripts/search.js "electric vehicles" --headlines --country gb > uk_headlines.json
node scripts/search.js "electric vehicles" --headlines --country de > de_headlines.json
# Compare volume
# jq '.totalResults' *_headlines.json#!/usr/bin/env node
const https = require('https');
const fs = require('fs');
const path = require('path');
// Load environment variables
function loadEnv() {
const envPaths = [
path.join(process.env.HOME, '.openclaw', '.env'),
path.join(__dirname, '..', '.env')
];
envPaths.forEach(envPath => {
if (fs.existsSync(envPath)) {
const envContent = fs.readFileSync(envPath, 'utf-8');
envContent.split('\n').forEach(line => {
const cleaned = line.trim();
if (!cleaned || cleaned.startsWith('#')) return;
const normalized = cleaned.startsWith('export ') ? cleaned.slice(7) : cleaned;
const match = normalized.match(/^([^#=]+)=(.*)$/);
if (match) {
process.env[match[1].trim()] = match[2].trim();
}
});
}
});
}
loadEnv();
const API_KEY = process.env.NEWSAPI_KEY;
function showUsage() {
console.log(`
Usage: node search.js <query> [options]
EVERYTHING ENDPOINT (default):
--hours N Search last N hours
--days N Search last N days (default: 7)
--weeks N Search last N weeks
--months N Search last N months
--from DATE Start date (YYYY-MM-DD)
--to DATE End date (YYYY-MM-DD)
--limit N Max results (default: 10, max: 100)
--page N Page number for pagination (default: 1)
--in FIELDS Search in: title, description, content (comma-separated)
--title-only Search only in article titles (qInTitle)
--sources IDS Comma-separated source IDs (max 20)
--domains LIST Include only these domains (comma-separated)
--exclude LIST Exclude these domains (comma-separated)
--lang CODE Language code (default: en, 'any' for all)
--sort METHOD Sort by: relevancy, date (publishedAt), popularity
TOP HEADLINES ENDPOINT:
--headlines Use top-headlines endpoint
--country CODE 2-letter country code (e.g., us, gb, de)
--category CAT Category: business, entertainment, general, health, science, sports, technology
Note: --headlines cannot mix --country/--category with --sources
Examples:
node search.js "manhunt" --days 3 --limit 5
node search.js "manhunt" --sources bbc-news,cnn --lang en
node search.js "manhunt" --domains nytimes.com,bbc.co.uk
node search.js "manhunt" --title-only --sort date
node search.js "trump" --headlines --country us --category politics
`);
}
function parseArgs(args) {
const options = {
query: null,
endpoint: 'everything',
timeWindow: { type: 'days', value: 7 },
limit: 10,
page: 1,
lang: 'en',
sort: 'relevancy',
searchIn: null,
titleOnly: false,
sources: null,
domains: null,
excludeDomains: null,
country: null,
category: null
};
for (let i = 0; i < args.length; i++) {
const arg = args[i];
if (arg === '--help' || arg === '-h') {
showUsage();
process.exit(0);
} else if (arg === '--headlines') {
options.endpoint = 'top-headlines';
} else if (arg === '--hours' && args[i + 1]) {
options.timeWindow = { type: 'hours', value: parseInt(args[++i]) };
} else if (arg === '--days' && args[i + 1]) {
options.timeWindow = { type: 'days', value: parseInt(args[++i]) };
} else if (arg === '--weeks' && args[i + 1]) {
options.timeWindow = { type: 'weeks', value: parseInt(args[++i]) };
} else if (arg === '--months' && args[i + 1]) {
options.timeWindow = { type: 'months', value: parseInt(args[++i]) };
} else if (arg === '--from' && args[i + 1]) {
options.fromDate = args[++i];
} else if (arg === '--to' && args[i + 1]) {
options.toDate = args[++i];
} else if (arg === '--limit' && args[i + 1]) {
options.limit = Math.min(parseInt(args[++i]), 100);
} else if (arg === '--page' && args[i + 1]) {
options.page = parseInt(args[++i]);
} else if (arg === '--lang' && args[i + 1]) {
options.lang = args[++i];
} else if (arg === '--sort' && args[i + 1]) {
const sortVal = args[++i];
options.sort = sortVal === 'date' ? 'publishedAt' : sortVal;
} else if (arg === '--in' && args[i + 1]) {
options.searchIn = args[++i];
} else if (arg === '--title-only') {
options.titleOnly = true;
} else if (arg === '--sources' && args[i + 1]) {
options.sources = args[++i];
} else if (arg === '--domains' && args[i + 1]) {
options.domains = args[++i];
} else if (arg === '--exclude' && args[i + 1]) {
options.excludeDomains = args[++i];
} else if (arg === '--country' && args[i + 1]) {
options.country = args[++i];
} else if (arg === '--category' && args[i + 1]) {
options.category = args[++i];
} else if (!arg.startsWith('--') && !options.query) {
options.query = arg;
}
}
return options;
}
function calculateDateRange(timeWindow) {
const now = new Date();
let fromDate = new Date();
switch (timeWindow.type) {
case 'hours':
fromDate.setHours(now.getHours() - timeWindow.value);
break;
case 'days':
fromDate.setDate(now.getDate() - timeWindow.value);
break;
case 'weeks':
fromDate.setDate(now.getDate() - (timeWindow.value * 7));
break;
case 'months':
fromDate.setMonth(now.getMonth() - timeWindow.value);
break;
}
return {
from: fromDate.toISOString().split('T')[0],
to: now.toISOString().split('T')[0]
};
}
function makeRequest(url) {
return new Promise((resolve, reject) => {
const options = new URL(url);
const requestOptions = {
hostname: options.hostname,
path: options.pathname + options.search,
method: 'GET',
headers: {
'User-Agent': 'NewsAPISearch/1.0 (Research Tool)'
}
};
const req = https.request(requestOptions, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
try {
resolve(JSON.parse(data));
} catch (e) {
reject(new Error('Invalid JSON response'));
}
});
});
req.on('error', reject);
req.setTimeout(30000, () => {
req.destroy();
reject(new Error('Request timeout'));
});
req.end();
});
}
async function searchEverything(query, options = {}) {
if (!API_KEY) {
throw new Error('NEWSAPI_KEY not found in environment. Add it to ~/.openclaw/.env');
}
let fromDate, toDate;
if (options.fromDate && options.toDate) {
fromDate = options.fromDate;
toDate = options.toDate;
} else if (options.timeWindow) {
const range = calculateDateRange(options.timeWindow);
fromDate = range.from;
toDate = range.to;
}
const params = new URLSearchParams({
pageSize: options.limit?.toString() || '10',
page: options.page?.toString() || '1',
sortBy: options.sort || 'relevancy',
apiKey: API_KEY
});
// Query parameter: use qInTitle or q
if (query) {
if (options.titleOnly) {
params.append('qInTitle', query);
} else {
params.append('q', query);
}
}
// Date filters
if (fromDate) params.append('from', fromDate);
if (toDate) params.append('to', toDate);
// Language (skip if 'any' or headlines mode where it's not supported)
if (options.lang && options.lang !== 'any') {
params.append('language', options.lang);
}
// Search fields
if (options.searchIn) {
params.append('searchIn', options.searchIn);
}
// Source and domain filters
if (options.sources) {
params.append('sources', options.sources);
}
if (options.domains) {
params.append('domains', options.domains);
}
if (options.excludeDomains) {
params.append('excludeDomains', options.excludeDomains);
}
const url = `https://newsapi.org/v2/everything?${params.toString()}`;
try {
const data = await makeRequest(url);
if (data.status === 'error') {
throw new Error(`NewsAPI error: ${data.message || 'Unknown error'}`);
}
return {
query,
endpoint: 'everything',
timeWindow: options.timeWindow,
fromDate,
toDate,
page: options.page || 1,
language: options.lang || 'any',
sortBy: options.sort || 'relevancy',
filters: {
sources: options.sources || null,
domains: options.domains || null,
excludeDomains: options.excludeDomains || null
},
totalResults: data.totalResults || 0,
returnedResults: data.articles?.length || 0,
results: (data.articles || []).map(article => ({
title: article.title,
url: article.url,
description: article.description,
content: article.content,
source: article.source?.name || 'Unknown',
author: article.author,
publishedAt: article.publishedAt,
urlToImage: article.urlToImage
}))
};
} catch (error) {
throw new Error(`Search failed: ${error.message}`);
}
}
async function searchHeadlines(query, options = {}) {
if (!API_KEY) {
throw new Error('NEWSAPI_KEY not found in environment. Add it to ~/.openclaw/.env');
}
const params = new URLSearchParams({
pageSize: options.limit?.toString() || '10',
page: options.page?.toString() || '1',
apiKey: API_KEY
});
if (query) {
params.append('q', query);
}
// Country and category (cannot mix with sources)
if (options.country && !options.sources) {
params.append('country', options.country);
}
if (options.category && !options.sources) {
params.append('category', options.category);
}
if (options.sources) {
params.append('sources', options.sources);
}
const url = `https://newsapi.org/v2/top-headlines?${params.toString()}`;
try {
const data = await makeRequest(url);
if (data.status === 'error') {
throw new Error(`NewsAPI error: ${data.message || 'Unknown error'}`);
}
return {
query,
endpoint: 'top-headlines',
country: options.country || null,
category: options.category || null,
page: options.page || 1,
totalResults: data.totalResults || 0,
returnedResults: data.articles?.length || 0,
results: (data.articles || []).map(article => ({
title: article.title,
url: article.url,
description: article.description,
content: article.content,
source: article.source?.name || 'Unknown',
author: article.author,
publishedAt: article.publishedAt,
urlToImage: article.urlToImage
}))
};
} catch (error) {
throw new Error(`Headlines search failed: ${error.message}`);
}
}
async function main() {
const args = process.argv.slice(2);
if (args.length === 0) {
showUsage();
process.exit(1);
}
const options = parseArgs(args);
// Validate API key
if (!API_KEY) {
console.error('Error: NEWSAPI_KEY not found');
console.error('Add NEWSAPI_KEY=your_key to ~/.openclaw/.env');
process.exit(1);
}
try {
let results;
if (options.endpoint === 'top-headlines') {
results = await searchHeadlines(options.query, {
country: options.country,
category: options.category,
sources: options.sources,
limit: options.limit,
page: options.page
});
} else {
results = await searchEverything(options.query, options);
}
console.log(JSON.stringify(results, null, 2));
} catch (error) {
console.error(`Error: ${error.message}`);
process.exit(1);
}
}
if (require.main === module) {
main();
}
module.exports = {
searchEverything,
searchHeadlines,
calculateDateRange,
makeRequest
};
#!/usr/bin/env node
const https = require('https');
const fs = require('fs');
const path = require('path');
// Load environment variables
function loadEnv() {
const envPath = path.join(process.env.HOME, '.openclaw', '.env');
if (fs.existsSync(envPath)) {
const envContent = fs.readFileSync(envPath, 'utf-8');
envContent.split('\n').forEach(line => {
const match = line.match(/^([^#=]+)=(.*)$/);
if (match) {
process.env[match[1].trim()] = match[2].trim();
}
});
}
}
loadEnv();
const API_KEY = process.env.NEWSAPI_KEY;
function showUsage() {
console.log(`
Usage: node sources.js [options]
List available news sources from NewsAPI.
Options:
--country CODE Filter by 2-letter country code (e.g., us, gb, de)
--category CAT Filter by category: business, entertainment, general, health, science, sports, technology
--lang CODE Filter by 2-letter language code (e.g., en, es, de)
--json Output raw JSON instead of formatted list
Examples:
node sources.js
node sources.js --country us
node sources.js --category business --lang en
node sources.js --json > sources.json
`);
}
function parseArgs(args) {
const options = {
country: null,
category: null,
language: null,
json: false
};
for (let i = 0; i < args.length; i++) {
const arg = args[i];
if (arg === '--help' || arg === '-h') {
showUsage();
process.exit(0);
} else if (arg === '--country' && args[i + 1]) {
options.country = args[++i];
} else if (arg === '--category' && args[i + 1]) {
options.category = args[++i];
} else if (arg === '--lang' && args[i + 1]) {
options.language = args[++i];
} else if (arg === '--json') {
options.json = true;
}
}
return options;
}
function makeRequest(url) {
return new Promise((resolve, reject) => {
const options = new URL(url);
const requestOptions = {
hostname: options.hostname,
path: options.pathname + options.search,
method: 'GET',
headers: {
'User-Agent': 'NewsAPISearch/1.0 (Research Tool)'
}
};
const req = https.request(requestOptions, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
try {
resolve(JSON.parse(data));
} catch (e) {
reject(new Error('Invalid JSON response'));
}
});
});
req.on('error', reject);
req.setTimeout(30000, () => {
req.destroy();
reject(new Error('Request timeout'));
});
req.end();
});
}
async function getSources(filters = {}) {
if (!API_KEY) {
throw new Error('NEWSAPI_KEY not found in environment. Add it to ~/.openclaw/.env');
}
const params = new URLSearchParams({
apiKey: API_KEY
});
if (filters.country) {
params.append('country', filters.country);
}
if (filters.category) {
params.append('category', filters.category);
}
if (filters.language) {
params.append('language', filters.language);
}
const url = `https://newsapi.org/v2/top-headlines/sources?${params.toString()}`;
try {
const data = await makeRequest(url);
if (data.status === 'error') {
throw new Error(`NewsAPI error: ${data.message || 'Unknown error'}`);
}
return data.sources || [];
} catch (error) {
throw new Error(`Failed to fetch sources: ${error.message}`);
}
}
function formatSources(sources) {
// Group by country
const byCountry = {};
sources.forEach(source => {
const country = source.country?.toUpperCase() || 'Unknown';
if (!byCountry[country]) {
byCountry[country] = [];
}
byCountry[country].push(source);
});
// Sort countries
const sortedCountries = Object.keys(byCountry).sort();
let output = `Found ${sources.length} sources\n`;
output += '='.repeat(50) + '\n\n';
for (const country of sortedCountries) {
output += `\n${country} (${byCountry[country].length} sources)\n`;
output += '-'.repeat(40) + '\n';
// Sort sources alphabetically
const sortedSources = byCountry[country].sort((a, b) => a.name.localeCompare(b.name));
for (const source of sortedSources) {
output += ` ${source.id}\n`;
output += ` Name: ${source.name}\n`;
output += ` Category: ${source.category || 'N/A'}\n`;
output += ` Language: ${source.language || 'N/A'}\n`;
output += ` URL: ${source.url}\n\n`;
}
}
return output;
}
async function main() {
const args = process.argv.slice(2);
if (args.includes('--help') || args.includes('-h')) {
showUsage();
process.exit(0);
}
const options = parseArgs(args);
if (!API_KEY) {
console.error('Error: NEWSAPI_KEY not found');
console.error('Add NEWSAPI_KEY=your_key to ~/.openclaw/.env');
process.exit(1);
}
try {
const sources = await getSources({
country: options.country,
category: options.category,
language: options.language
});
if (options.json) {
console.log(JSON.stringify(sources, null, 2));
} else {
console.log(formatSources(sources));
}
} catch (error) {
console.error(`Error: ${error.message}`);
process.exit(1);
}
}
if (require.main === module) {
main();
}
module.exports = { getSources, makeRequest };