@lacspace/trends 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -36,7 +36,7 @@ Each source is bring-your-own-`fetch`; a failing source becomes a `warnings` ent
36
36
 
37
37
  - **`trends(options)`** → `{ trends, sources, warnings }`. Options: `geo`, `sources`, `youtubeApiKey`, `wikipediaProject`, `relevanceTo` (+ `minRelevance`), `fetch`, `signal`.
38
38
  - **`mergeTrends(raw[])`** → merges raw trends across scripts (Dashain ≡ दशैं) and boosts multi-source agreement.
39
- - **`relevanceTo(trends, story, { min? })`** → attaches a 0–1 `relevance` (token overlap via `@lacspace/keyphrase` + `@lacspace/translit`), optionally filtering.
39
+ - **`relevanceTo(trends, story, { min? })`** → attaches a 0–1 `relevance` (token overlap via `@lacspace/keyphrase` + `@lacspace/translit`), optionally filtering. From 1.1.0 a shared common word is never enough. A trend must match on a distinctive term (proper noun, entity, rare word) or as a whole phrase. Name-like trends (`River Phoenix`) need the full name in the story, and single common-word trends are capped at 0.2. Plurals and -ed/-ing forms are stemmed, so `floods` matches `flood`. `COMMON_TERMS` is exported.
40
40
  - **`hashtagsFor(terms, platform, { banned?, max?, extra? })`** → camel-cased, de-duplicated hashtags within the platform limit (IG 30, X 3, TikTok 6…), dropping banned/shadow-ban-prone tags.
41
41
  - **Source fns** `googleTrendsDaily`, `youtubeMostPopular`, `wikipediaTop` and `parseGoogleTrendsRss` are exported.
42
42
  - **`describe()`** → machine-readable command schema for an AI "conductor".
package/dist/index.cjs CHANGED
@@ -90,7 +90,7 @@ function decode(s) {
90
90
  }
91
91
 
92
92
  // src/index.ts
93
- var VERSION = "1.0.0";
93
+ var VERSION = "1.1.0";
94
94
  function keyOf(term) {
95
95
  const roman = translit.detectScript(term) === "ne" ? translit.transliterate(term, { from: "ne", to: "en" }) : term;
96
96
  return roman.toLowerCase().replace(/[^a-z0-9]+/g, "");
@@ -128,17 +128,34 @@ function mergeTrends(raws) {
128
128
  }
129
129
  return out.sort((a, b) => b.score - a.score);
130
130
  }
131
+ var COMMON_TERMS = "river lake water rain flood floods storm fire earthquake road bridge city town village country nation state world people man woman men women child children family home house school college university hospital market price prices money bank job jobs work worker news report story video photo live today tomorrow yesterday week month year day night time new old big small high low first last top best good bad open close update government minister ministry police army court law election vote party leader president prime team match game cup league final season player fans film movie song music show star stars love life death dead killed kill dies died attack crash accident case cases test result results index share shares stock stocks gold oil fuel power energy weather heat cold snow wind air sky sun moon park forest mountain hill valley animal animals bird birds dog cat tiger rhino rhinos elephant phone app online internet data tech car bus train plane flight airport festival holiday day nepal india china usa america global international national local district province area region zone north south east west red blue black white green golden king queen prince lord saint new big".split(/\s+/);
132
+ var COMMON = /* @__PURE__ */ new Set([...COMMON_TERMS, ...keyphrase.ENGLISH_STOPWORDS.map((w) => w.toLowerCase())]);
133
+ function stem(w) {
134
+ if (w.length <= 4) return w;
135
+ return w.replace(/(?:ing|ed|es|s)$/, "") || w;
136
+ }
137
+ var isCommon = (w) => COMMON.has(w) || COMMON.has(stem(w));
138
+ var nameLike = (term) => /^\p{Lu}[\p{Ll}'.-]+(?:\s+\p{Lu}[\p{Ll}'.-]+){1,2}$/u.test(term.trim());
131
139
  function relevanceTo(trends2, story, options = {}) {
132
140
  const kp = keyphrase.keyphrase(story, { max: 20 });
133
141
  const storyTokens = /* @__PURE__ */ new Set();
134
- for (const p of kp.phrases ?? []) for (const t of tokens(p.phrase)) storyTokens.add(t);
135
- for (const t of tokens(story)) storyTokens.add(t);
142
+ for (const p of kp.phrases ?? []) for (const t of tokens(p.phrase)) storyTokens.add(stem(t));
143
+ for (const t of tokens(story)) storyTokens.add(stem(t));
144
+ const storyKey = " " + tokens(story).map(stem).join(" ") + " ";
136
145
  const scored = trends2.map((t) => {
137
146
  const tt = tokens(t.term);
138
147
  if (!tt.length || storyTokens.size === 0) return { ...t, relevance: 0 };
139
- const hits = tt.filter((w) => storyTokens.has(w)).length;
140
- const rel = round(hits / tt.length);
141
- return { ...t, relevance: rel };
148
+ const has = (w) => storyTokens.has(stem(w));
149
+ const hits = tt.filter(has).length;
150
+ const phrase = tt.length > 1 && storyKey.includes(" " + tt.map(stem).join(" ") + " ");
151
+ const distinctive = tt.filter((w) => !isCommon(w));
152
+ let rel;
153
+ if (phrase) rel = 1;
154
+ else if (tt.length === 1) rel = hits ? distinctive.length ? 1 : 0.2 : 0;
155
+ else if (nameLike(t.term)) rel = hits === tt.length ? 1 : 0;
156
+ else if (!distinctive.length) rel = Math.min(0.2, hits / tt.length);
157
+ else rel = distinctive.every(has) ? hits / tt.length : 0;
158
+ return { ...t, relevance: round(rel) };
142
159
  });
143
160
  return options.min !== void 0 ? scored.filter((t) => (t.relevance ?? 0) >= options.min) : scored;
144
161
  }
@@ -216,6 +233,7 @@ function round(n) {
216
233
  return Math.round(n * 1e3) / 1e3;
217
234
  }
218
235
 
236
+ exports.COMMON_TERMS = COMMON_TERMS;
219
237
  exports.HASHTAG_LIMITS = HASHTAG_LIMITS;
220
238
  exports.describe = describe;
221
239
  exports.googleTrendsDaily = googleTrendsDaily;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/sources.ts","../src/index.ts"],"names":["detectScript","transliterate","trends","keyphrase"],"mappings":"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","file":"index.cjs","sourcesContent":["export type TrendSource = \"google\" | \"youtube\" | \"wikipedia\";\n\nexport interface RawTrend {\n term: string;\n source: TrendSource;\n /** 0–1 weight within this source (1 = top). */\n weight: number;\n /** Approx search traffic / views, when the source gives it. */\n volume?: number;\n /** Supporting links (news items / article). */\n links?: string[];\n}\n\nexport type FetchLike = (url: string, init?: { headers?: Record<string, string>; signal?: AbortSignal }) => Promise<{ ok: boolean; status: number; text(): Promise<string>; json(): Promise<any> }>;\n\nfunction fetchImpl(f?: FetchLike): FetchLike {\n const impl = f ?? (globalThis as any).fetch;\n if (!impl) throw new Error(\"trends: no fetch available — pass options.fetch\");\n return impl;\n}\n\nfunction rankWeights(n: number): number[] {\n // Linear decay from 1 (rank 1) to ~0.1 (rank n).\n return Array.from({ length: n }, (_, i) => (n <= 1 ? 1 : 1 - (0.9 * i) / (n - 1)));\n}\n\nfunction parseTraffic(s: string | undefined): number | undefined {\n if (!s) return undefined;\n const m = s.replace(/,/g, \"\").match(/(\\d+)\\s*(\\+)?/);\n return m ? Number(m[1]) : undefined;\n}\n\n// --- Google Trends daily RSS (geo=NP by default) -----------------------------------\n/** Fetch + parse the Google Trends \"daily trending searches\" RSS for a region. */\nexport async function googleTrendsDaily(options: { geo?: string; fetch?: FetchLike; signal?: AbortSignal } = {}): Promise<RawTrend[]> {\n const geo = options.geo ?? \"NP\";\n const url = `https://trends.google.com/trends/trendingsearches/daily/rss?geo=${encodeURIComponent(geo)}`;\n const res = await fetchImpl(options.fetch)(url, { signal: options.signal });\n if (!res.ok) throw new Error(`google-trends ${res.status}`);\n return parseGoogleTrendsRss(await res.text());\n}\n\n/** Parse Google Trends daily RSS (title + ht:approx_traffic + ht:news_item links). */\nexport function parseGoogleTrendsRss(xml: string): RawTrend[] {\n const items = [...xml.matchAll(/<item>([\\s\\S]*?)<\\/item>/g)].map((m) => m[1]!);\n const out: { term: string; volume?: number; links: string[] }[] = [];\n for (const item of items) {\n const title = decode(first(item, /<title>([\\s\\S]*?)<\\/title>/));\n if (!title) continue;\n const volume = parseTraffic(first(item, /<ht:approx_traffic>([\\s\\S]*?)<\\/ht:approx_traffic>/));\n const links = [...item.matchAll(/<ht:news_item_url>([\\s\\S]*?)<\\/ht:news_item_url>/g)].map((m) => decode(m[1]!));\n out.push({ term: title, volume, links });\n }\n const w = rankWeights(out.length);\n return out.map((o, i) => ({ term: o.term, source: \"google\", weight: w[i]!, volume: o.volume, links: o.links }));\n}\n\n// --- YouTube Data API v3: most popular in a region (needs an API key) --------------\nexport async function youtubeMostPopular(options: { apiKey: string; regionCode?: string; maxResults?: number; fetch?: FetchLike; signal?: AbortSignal }): Promise<RawTrend[]> {\n if (!options.apiKey) return [];\n const u = new URL(\"https://www.googleapis.com/youtube/v3/videos\");\n u.searchParams.set(\"part\", \"snippet,statistics\");\n u.searchParams.set(\"chart\", \"mostPopular\");\n u.searchParams.set(\"regionCode\", options.regionCode ?? \"NP\");\n u.searchParams.set(\"maxResults\", String(options.maxResults ?? 25));\n u.searchParams.set(\"key\", options.apiKey);\n const res = await fetchImpl(options.fetch)(u.toString(), { signal: options.signal });\n if (!res.ok) throw new Error(`youtube ${res.status}`);\n const data = await res.json();\n const rows = (data.items ?? []) as any[];\n const w = rankWeights(rows.length);\n return rows.map((it, i) => ({\n term: it.snippet?.title ?? \"\",\n source: \"youtube\" as const,\n weight: w[i]!,\n volume: it.statistics?.viewCount ? Number(it.statistics.viewCount) : undefined,\n links: it.id ? [`https://youtu.be/${it.id}`] : [],\n })).filter((t) => t.term);\n}\n\n// --- Wikipedia pageviews top (free REST API, no key) -------------------------------\nexport async function wikipediaTop(options: { project?: string; date?: Date; fetch?: FetchLike; signal?: AbortSignal } = {}): Promise<RawTrend[]> {\n const project = options.project ?? \"en.wikipedia\";\n const d = options.date ?? new Date(Date.now() - 86400000); // yesterday (today may be incomplete)\n const yyyy = d.getUTCFullYear();\n const mm = String(d.getUTCMonth() + 1).padStart(2, \"0\");\n const dd = String(d.getUTCDate()).padStart(2, \"0\");\n const url = `https://wikimedia.org/api/rest_v1/metrics/pageviews/top/${project}/all-access/${yyyy}/${mm}/${dd}`;\n const res = await fetchImpl(options.fetch)(url, { headers: { \"User-Agent\": \"lacspace-trends/1.0 (contact@lacspace.com)\" }, signal: options.signal });\n if (!res.ok) throw new Error(`wikipedia ${res.status}`);\n const data = await res.json();\n const articles = (data.items?.[0]?.articles ?? []) as any[];\n const filtered = articles.filter((a) => !/^(Main_Page|Special:|Wikipedia:|विशेष:|मुख्य_पृष्ठ)/.test(a.article));\n const w = rankWeights(filtered.length);\n return filtered.map((a, i) => ({\n term: String(a.article).replace(/_/g, \" \"),\n source: \"wikipedia\" as const,\n weight: w[i]!,\n volume: a.views,\n links: [`https://${project}.org/wiki/${encodeURIComponent(a.article)}`],\n }));\n}\n\nfunction first(s: string, re: RegExp): string | undefined {\n const m = s.match(re);\n return m ? m[1]!.trim() : undefined;\n}\nfunction decode(s: string | undefined): string {\n return (s ?? \"\")\n .replace(/<!\\[CDATA\\[([\\s\\S]*?)\\]\\]>/g, \"$1\")\n .replace(/&amp;/g, \"&\").replace(/&lt;/g, \"<\").replace(/&gt;/g, \">\").replace(/&quot;/g, '\"').replace(/&#39;/g, \"'\")\n .trim();\n}\n","import { keyphrase } from \"@lacspace/keyphrase\";\nimport { transliterate, detectScript } from \"@lacspace/translit\";\nimport { FetchLike, googleTrendsDaily, RawTrend, TrendSource, wikipediaTop, youtubeMostPopular } from \"./sources.js\";\n\nexport * from \"./sources.js\";\n\nconst VERSION = \"1.0.0\";\n\nexport interface Trend {\n /** Display term (original casing/script of the strongest source). */\n term: string;\n /** Merged score, higher = more trending (multi-source terms rank up). */\n score: number;\n sources: TrendSource[];\n volume?: number;\n links: string[];\n /** 0–1 relevance to a story, set by relevanceTo / trends({ relevanceTo }). */\n relevance?: number;\n}\n\n/** A cross-script, lowercased key so \"Dashain\" and \"दशैं\" merge. */\nfunction keyOf(term: string): string {\n const roman = detectScript(term) === \"ne\" ? transliterate(term, { from: \"ne\", to: \"en\" }) : term;\n return roman.toLowerCase().replace(/[^a-z0-9]+/g, \"\");\n}\n\nfunction tokens(s: string): string[] {\n const roman = detectScript(s) === \"ne\" ? transliterate(s, { from: \"ne\", to: \"en\" }) : s;\n return (roman.toLowerCase().match(/[a-z0-9]+/g) ?? []).filter((w) => w.length > 2);\n}\n\n/** Merge raw trends from several sources into one scored, de-duplicated list. */\nexport function mergeTrends(raws: RawTrend[]): Trend[] {\n const groups = new Map<string, { best: RawTrend; score: number; sources: Set<TrendSource>; links: Set<string>; volume: number }>();\n for (const r of raws) {\n const k = keyOf(r.term);\n if (!k) continue;\n let g = groups.get(k);\n if (!g) {\n g = { best: r, score: 0, sources: new Set(), links: new Set(), volume: 0 };\n groups.set(k, g);\n }\n g.score += r.weight;\n g.sources.add(r.source);\n for (const l of r.links ?? []) g.links.add(l);\n if (r.volume) g.volume = Math.max(g.volume, r.volume);\n if (r.weight > g.best.weight) g.best = r; // strongest source sets the display term\n }\n const out: Trend[] = [];\n for (const g of groups.values()) {\n // Multi-source agreement is a strong signal → multiply by a diversity boost.\n const boost = 1 + 0.5 * (g.sources.size - 1);\n out.push({\n term: g.best.term,\n score: round(g.score * boost),\n sources: [...g.sources],\n volume: g.volume || undefined,\n links: [...g.links].slice(0, 5),\n });\n }\n return out.sort((a, b) => b.score - a.score);\n}\n\n/** Score each trend's relevance to a story (0–1) and attach it; optionally filter. */\nexport function relevanceTo(trends: Trend[], story: string, options: { min?: number } = {}): Trend[] {\n const kp = keyphrase(story, { max: 20 } as any);\n const storyTokens = new Set<string>();\n for (const p of kp.phrases ?? []) for (const t of tokens(p.phrase)) storyTokens.add(t);\n for (const t of tokens(story)) storyTokens.add(t);\n const scored = trends.map((t) => {\n const tt = tokens(t.term);\n if (!tt.length || storyTokens.size === 0) return { ...t, relevance: 0 };\n const hits = tt.filter((w) => storyTokens.has(w)).length;\n const rel = round(hits / tt.length);\n return { ...t, relevance: rel };\n });\n return options.min !== undefined ? scored.filter((t) => (t.relevance ?? 0) >= options.min!) : scored;\n}\n\n// --- hashtags ----------------------------------------------------------------------\nexport const HASHTAG_LIMITS: Record<string, number> = {\n instagram: 30, tiktok: 6, x: 3, facebook: 3, youtube: 15, threads: 5, linkedin: 5, telegram: 10,\n};\n// A conservative starter list of broken/shadow-ban-prone tags; callers extend.\nconst DEFAULT_BANNED = new Set([\"followme\", \"follow4follow\", \"like4like\", \"nepaltiktok\", \"viral\", \"fyp\"]);\n\nfunction toHashtag(term: string): string {\n const roman = detectScript(term) === \"ne\" ? transliterate(term, { from: \"ne\", to: \"en\" }) : term;\n const camel = roman.replace(/[^a-zA-Z0-9]+/g, \" \").trim().split(/\\s+/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(\"\");\n return camel ? \"#\" + camel : \"\";\n}\n\n/** Build a platform-appropriate hashtag set from trend terms (+ optional extra tags). */\nexport function hashtagsFor(\n terms: string[],\n platform: keyof typeof HASHTAG_LIMITS | string,\n options: { banned?: string[]; max?: number; extra?: string[] } = {},\n): string[] {\n const limit = options.max ?? HASHTAG_LIMITS[platform] ?? 10;\n const banned = new Set([...DEFAULT_BANNED, ...(options.banned ?? []).map((b) => b.toLowerCase().replace(/^#/, \"\"))]);\n const seen = new Set<string>();\n const out: string[] = [];\n for (const raw of [...terms, ...(options.extra ?? [])]) {\n const tag = toHashtag(raw);\n const bare = tag.slice(1).toLowerCase();\n if (!tag || bare.length < 2 || banned.has(bare) || seen.has(bare)) continue;\n seen.add(bare);\n out.push(tag);\n if (out.length >= limit) break;\n }\n return out;\n}\n\nexport interface TrendsOptions {\n geo?: string;\n /** Sources to query. Default: google + wikipedia (youtube added iff youtubeApiKey). */\n sources?: TrendSource[];\n youtubeApiKey?: string;\n wikipediaProject?: string;\n /** Attach + filter by relevance to this story. */\n relevanceTo?: string;\n minRelevance?: number;\n fetch?: FetchLike;\n signal?: AbortSignal;\n}\n\nexport interface TrendsResult {\n trends: Trend[];\n sources: TrendSource[];\n warnings: string[];\n}\n\n/** Fetch, merge and score trends from the configured official/public sources. */\nexport async function trends(options: TrendsOptions = {}): Promise<TrendsResult> {\n const want = options.sources ?? ([\"google\", \"wikipedia\", ...(options.youtubeApiKey ? [\"youtube\"] as const : [])] as TrendSource[]);\n const warnings: string[] = [];\n const used: TrendSource[] = [];\n const raws: RawTrend[] = [];\n const run = async (s: TrendSource, fn: () => Promise<RawTrend[]>) => {\n try {\n const r = await fn();\n raws.push(...r);\n used.push(s);\n } catch (e) {\n warnings.push(`${s}: ${(e as Error).message}`);\n }\n };\n await Promise.all(\n want.map((s) => {\n if (s === \"google\") return run(\"google\", () => googleTrendsDaily({ geo: options.geo, fetch: options.fetch, signal: options.signal }));\n if (s === \"wikipedia\") return run(\"wikipedia\", () => wikipediaTop({ project: options.wikipediaProject, fetch: options.fetch, signal: options.signal }));\n if (s === \"youtube\" && options.youtubeApiKey) return run(\"youtube\", () => youtubeMostPopular({ apiKey: options.youtubeApiKey!, regionCode: options.geo, fetch: options.fetch, signal: options.signal }));\n return Promise.resolve();\n }),\n );\n let merged = mergeTrends(raws);\n if (options.relevanceTo) merged = relevanceTo(merged, options.relevanceTo, { min: options.minRelevance });\n return { trends: merged, sources: used, warnings };\n}\n\n/** Machine-readable descriptor for an AI \"conductor\". */\nexport function describe() {\n return {\n name: \"@lacspace/trends\",\n version: VERSION,\n summary: \"Trending topics for a region from official/public sources only (Google Trends RSS, YouTube mostPopular, Wikipedia pageviews), merged + scored, with story-relevance gating and per-platform hashtag sets.\",\n sources: [\"google\", \"youtube\", \"wikipedia\"],\n commands: [\n { name: \"trends\", input: { type: \"object\", properties: { geo: { type: \"string\" }, sources: { type: \"array\" }, youtubeApiKey: { type: \"string\" }, relevanceTo: { type: \"string\" }, minRelevance: { type: \"number\" } } }, output: \"{ trends:[{term,score,sources,volume,links,relevance}], sources, warnings }\" },\n { name: \"relevanceTo\", input: { type: \"object\", properties: { trends: { type: \"array\" }, story: { type: \"string\" }, min: { type: \"number\" } }, required: [\"trends\", \"story\"] }, output: \"Trend[] (with relevance)\" },\n { name: \"hashtagsFor\", input: { type: \"object\", properties: { terms: { type: \"array\" }, platform: { type: \"string\" }, max: { type: \"integer\" }, banned: { type: \"array\" } }, required: [\"terms\", \"platform\"] }, output: \"string[]\" },\n ],\n };\n}\n\nfunction round(n: number): number {\n return Math.round(n * 1000) / 1000;\n}\n"]}
1
+ {"version":3,"sources":["../src/sources.ts","../src/index.ts"],"names":["detectScript","transliterate","ENGLISH_STOPWORDS","trends","keyphrase"],"mappings":"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","file":"index.cjs","sourcesContent":["export type TrendSource = \"google\" | \"youtube\" | \"wikipedia\";\n\nexport interface RawTrend {\n term: string;\n source: TrendSource;\n /** 0–1 weight within this source (1 = top). */\n weight: number;\n /** Approx search traffic / views, when the source gives it. */\n volume?: number;\n /** Supporting links (news items / article). */\n links?: string[];\n}\n\nexport type FetchLike = (url: string, init?: { headers?: Record<string, string>; signal?: AbortSignal }) => Promise<{ ok: boolean; status: number; text(): Promise<string>; json(): Promise<any> }>;\n\nfunction fetchImpl(f?: FetchLike): FetchLike {\n const impl = f ?? (globalThis as any).fetch;\n if (!impl) throw new Error(\"trends: no fetch available — pass options.fetch\");\n return impl;\n}\n\nfunction rankWeights(n: number): number[] {\n // Linear decay from 1 (rank 1) to ~0.1 (rank n).\n return Array.from({ length: n }, (_, i) => (n <= 1 ? 1 : 1 - (0.9 * i) / (n - 1)));\n}\n\nfunction parseTraffic(s: string | undefined): number | undefined {\n if (!s) return undefined;\n const m = s.replace(/,/g, \"\").match(/(\\d+)\\s*(\\+)?/);\n return m ? Number(m[1]) : undefined;\n}\n\n// --- Google Trends daily RSS (geo=NP by default) -----------------------------------\n/** Fetch + parse the Google Trends \"daily trending searches\" RSS for a region. */\nexport async function googleTrendsDaily(options: { geo?: string; fetch?: FetchLike; signal?: AbortSignal } = {}): Promise<RawTrend[]> {\n const geo = options.geo ?? \"NP\";\n const url = `https://trends.google.com/trends/trendingsearches/daily/rss?geo=${encodeURIComponent(geo)}`;\n const res = await fetchImpl(options.fetch)(url, { signal: options.signal });\n if (!res.ok) throw new Error(`google-trends ${res.status}`);\n return parseGoogleTrendsRss(await res.text());\n}\n\n/** Parse Google Trends daily RSS (title + ht:approx_traffic + ht:news_item links). */\nexport function parseGoogleTrendsRss(xml: string): RawTrend[] {\n const items = [...xml.matchAll(/<item>([\\s\\S]*?)<\\/item>/g)].map((m) => m[1]!);\n const out: { term: string; volume?: number; links: string[] }[] = [];\n for (const item of items) {\n const title = decode(first(item, /<title>([\\s\\S]*?)<\\/title>/));\n if (!title) continue;\n const volume = parseTraffic(first(item, /<ht:approx_traffic>([\\s\\S]*?)<\\/ht:approx_traffic>/));\n const links = [...item.matchAll(/<ht:news_item_url>([\\s\\S]*?)<\\/ht:news_item_url>/g)].map((m) => decode(m[1]!));\n out.push({ term: title, volume, links });\n }\n const w = rankWeights(out.length);\n return out.map((o, i) => ({ term: o.term, source: \"google\", weight: w[i]!, volume: o.volume, links: o.links }));\n}\n\n// --- YouTube Data API v3: most popular in a region (needs an API key) --------------\nexport async function youtubeMostPopular(options: { apiKey: string; regionCode?: string; maxResults?: number; fetch?: FetchLike; signal?: AbortSignal }): Promise<RawTrend[]> {\n if (!options.apiKey) return [];\n const u = new URL(\"https://www.googleapis.com/youtube/v3/videos\");\n u.searchParams.set(\"part\", \"snippet,statistics\");\n u.searchParams.set(\"chart\", \"mostPopular\");\n u.searchParams.set(\"regionCode\", options.regionCode ?? \"NP\");\n u.searchParams.set(\"maxResults\", String(options.maxResults ?? 25));\n u.searchParams.set(\"key\", options.apiKey);\n const res = await fetchImpl(options.fetch)(u.toString(), { signal: options.signal });\n if (!res.ok) throw new Error(`youtube ${res.status}`);\n const data = await res.json();\n const rows = (data.items ?? []) as any[];\n const w = rankWeights(rows.length);\n return rows.map((it, i) => ({\n term: it.snippet?.title ?? \"\",\n source: \"youtube\" as const,\n weight: w[i]!,\n volume: it.statistics?.viewCount ? Number(it.statistics.viewCount) : undefined,\n links: it.id ? [`https://youtu.be/${it.id}`] : [],\n })).filter((t) => t.term);\n}\n\n// --- Wikipedia pageviews top (free REST API, no key) -------------------------------\nexport async function wikipediaTop(options: { project?: string; date?: Date; fetch?: FetchLike; signal?: AbortSignal } = {}): Promise<RawTrend[]> {\n const project = options.project ?? \"en.wikipedia\";\n const d = options.date ?? new Date(Date.now() - 86400000); // yesterday (today may be incomplete)\n const yyyy = d.getUTCFullYear();\n const mm = String(d.getUTCMonth() + 1).padStart(2, \"0\");\n const dd = String(d.getUTCDate()).padStart(2, \"0\");\n const url = `https://wikimedia.org/api/rest_v1/metrics/pageviews/top/${project}/all-access/${yyyy}/${mm}/${dd}`;\n const res = await fetchImpl(options.fetch)(url, { headers: { \"User-Agent\": \"lacspace-trends/1.0 (contact@lacspace.com)\" }, signal: options.signal });\n if (!res.ok) throw new Error(`wikipedia ${res.status}`);\n const data = await res.json();\n const articles = (data.items?.[0]?.articles ?? []) as any[];\n const filtered = articles.filter((a) => !/^(Main_Page|Special:|Wikipedia:|विशेष:|मुख्य_पृष्ठ)/.test(a.article));\n const w = rankWeights(filtered.length);\n return filtered.map((a, i) => ({\n term: String(a.article).replace(/_/g, \" \"),\n source: \"wikipedia\" as const,\n weight: w[i]!,\n volume: a.views,\n links: [`https://${project}.org/wiki/${encodeURIComponent(a.article)}`],\n }));\n}\n\nfunction first(s: string, re: RegExp): string | undefined {\n const m = s.match(re);\n return m ? m[1]!.trim() : undefined;\n}\nfunction decode(s: string | undefined): string {\n return (s ?? \"\")\n .replace(/<!\\[CDATA\\[([\\s\\S]*?)\\]\\]>/g, \"$1\")\n .replace(/&amp;/g, \"&\").replace(/&lt;/g, \"<\").replace(/&gt;/g, \">\").replace(/&quot;/g, '\"').replace(/&#39;/g, \"'\")\n .trim();\n}\n","import { ENGLISH_STOPWORDS, keyphrase } from \"@lacspace/keyphrase\";\nimport { transliterate, detectScript } from \"@lacspace/translit\";\nimport { FetchLike, googleTrendsDaily, RawTrend, TrendSource, wikipediaTop, youtubeMostPopular } from \"./sources.js\";\n\nexport * from \"./sources.js\";\n\nconst VERSION = \"1.1.0\";\n\nexport interface Trend {\n /** Display term (original casing/script of the strongest source). */\n term: string;\n /** Merged score, higher = more trending (multi-source terms rank up). */\n score: number;\n sources: TrendSource[];\n volume?: number;\n links: string[];\n /** 0–1 relevance to a story, set by relevanceTo / trends({ relevanceTo }). */\n relevance?: number;\n}\n\n/** A cross-script, lowercased key so \"Dashain\" and \"दशैं\" merge. */\nfunction keyOf(term: string): string {\n const roman = detectScript(term) === \"ne\" ? transliterate(term, { from: \"ne\", to: \"en\" }) : term;\n return roman.toLowerCase().replace(/[^a-z0-9]+/g, \"\");\n}\n\nfunction tokens(s: string): string[] {\n const roman = detectScript(s) === \"ne\" ? transliterate(s, { from: \"ne\", to: \"en\" }) : s;\n return (roman.toLowerCase().match(/[a-z0-9]+/g) ?? []).filter((w) => w.length > 2);\n}\n\n/** Merge raw trends from several sources into one scored, de-duplicated list. */\nexport function mergeTrends(raws: RawTrend[]): Trend[] {\n const groups = new Map<string, { best: RawTrend; score: number; sources: Set<TrendSource>; links: Set<string>; volume: number }>();\n for (const r of raws) {\n const k = keyOf(r.term);\n if (!k) continue;\n let g = groups.get(k);\n if (!g) {\n g = { best: r, score: 0, sources: new Set(), links: new Set(), volume: 0 };\n groups.set(k, g);\n }\n g.score += r.weight;\n g.sources.add(r.source);\n for (const l of r.links ?? []) g.links.add(l);\n if (r.volume) g.volume = Math.max(g.volume, r.volume);\n if (r.weight > g.best.weight) g.best = r; // strongest source sets the display term\n }\n const out: Trend[] = [];\n for (const g of groups.values()) {\n // Multi-source agreement is a strong signal → multiply by a diversity boost.\n const boost = 1 + 0.5 * (g.sources.size - 1);\n out.push({\n term: g.best.term,\n score: round(g.score * boost),\n sources: [...g.sources],\n volume: g.volume || undefined,\n links: [...g.links].slice(0, 5),\n });\n }\n return out.sort((a, b) => b.score - a.score);\n}\n\n/**\n * Everyday nouns/verbs that are too generic to make a trend relevant on their own\n * (\"river\" must not pull in \"River Phoenix\"). Stop-words come from @lacspace/keyphrase.\n */\nexport const COMMON_TERMS: string[] = (\n \"river lake water rain flood floods storm fire earthquake road bridge city town village country nation state world people man woman men women \" +\n \"child children family home house school college university hospital market price prices money bank job jobs work worker news report story video \" +\n \"photo live today tomorrow yesterday week month year day night time new old big small high low first last top best good bad open close update \" +\n \"government minister ministry police army court law election vote party leader president prime team match game cup league final season player \" +\n \"fans film movie song music show star stars love life death dead killed kill dies died attack crash accident case cases test result results \" +\n \"index share shares stock stocks gold oil fuel power energy weather heat cold snow wind air sky sun moon park forest mountain hill valley \" +\n \"animal animals bird birds dog cat tiger rhino rhinos elephant phone app online internet data tech car bus train plane flight airport \" +\n \"festival holiday day nepal india china usa america global international national local district province area region zone north south east west \" +\n \"red blue black white green golden king queen prince lord saint new big\"\n).split(/\\s+/);\nconst COMMON = new Set([...COMMON_TERMS, ...ENGLISH_STOPWORDS.map((w) => w.toLowerCase())]);\n\n/** Crude English stem so \"floods\"/\"flooded\"/\"flooding\" meet \"flood\". */\nfunction stem(w: string): string {\n if (w.length <= 4) return w;\n return w.replace(/(?:ing|ed|es|s)$/, \"\") || w;\n}\nconst isCommon = (w: string) => COMMON.has(w) || COMMON.has(stem(w));\n/** Title-cased 2–3 Latin words, e.g. \"River Phoenix\" — treat as a possible person name. */\nconst nameLike = (term: string) => /^\\p{Lu}[\\p{Ll}'.-]+(?:\\s+\\p{Lu}[\\p{Ll}'.-]+){1,2}$/u.test(term.trim());\n\n/**\n * Score each trend's relevance to a story (0–1) and attach it; optionally filter.\n *\n * A shared common word is never enough: a trend counts only when it matches the story on a\n * distinctive term (a proper noun / entity / rare word) or as a whole phrase. Name-like trends\n * (\"River Phoenix\") need every word of the name in the story. A single common-word trend\n * (\"Flood\") is capped at 0.2.\n */\nexport function relevanceTo(trends: Trend[], story: string, options: { min?: number } = {}): Trend[] {\n const kp = keyphrase(story, { max: 20 } as any);\n const storyTokens = new Set<string>();\n for (const p of kp.phrases ?? []) for (const t of tokens(p.phrase)) storyTokens.add(stem(t));\n for (const t of tokens(story)) storyTokens.add(stem(t));\n const storyKey = \" \" + tokens(story).map(stem).join(\" \") + \" \";\n const scored = trends.map((t) => {\n const tt = tokens(t.term);\n if (!tt.length || storyTokens.size === 0) return { ...t, relevance: 0 };\n const has = (w: string) => storyTokens.has(stem(w));\n const hits = tt.filter(has).length;\n const phrase = tt.length > 1 && storyKey.includes(\" \" + tt.map(stem).join(\" \") + \" \");\n const distinctive = tt.filter((w) => !isCommon(w));\n let rel: number;\n if (phrase) rel = 1;\n else if (tt.length === 1) rel = hits ? (distinctive.length ? 1 : 0.2) : 0;\n else if (nameLike(t.term)) rel = hits === tt.length ? 1 : 0;\n else if (!distinctive.length) rel = Math.min(0.2, hits / tt.length);\n else rel = distinctive.every(has) ? hits / tt.length : 0;\n return { ...t, relevance: round(rel) };\n });\n return options.min !== undefined ? scored.filter((t) => (t.relevance ?? 0) >= options.min!) : scored;\n}\n\n// --- hashtags ----------------------------------------------------------------------\nexport const HASHTAG_LIMITS: Record<string, number> = {\n instagram: 30, tiktok: 6, x: 3, facebook: 3, youtube: 15, threads: 5, linkedin: 5, telegram: 10,\n};\n// A conservative starter list of broken/shadow-ban-prone tags; callers extend.\nconst DEFAULT_BANNED = new Set([\"followme\", \"follow4follow\", \"like4like\", \"nepaltiktok\", \"viral\", \"fyp\"]);\n\nfunction toHashtag(term: string): string {\n const roman = detectScript(term) === \"ne\" ? transliterate(term, { from: \"ne\", to: \"en\" }) : term;\n const camel = roman.replace(/[^a-zA-Z0-9]+/g, \" \").trim().split(/\\s+/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(\"\");\n return camel ? \"#\" + camel : \"\";\n}\n\n/** Build a platform-appropriate hashtag set from trend terms (+ optional extra tags). */\nexport function hashtagsFor(\n terms: string[],\n platform: keyof typeof HASHTAG_LIMITS | string,\n options: { banned?: string[]; max?: number; extra?: string[] } = {},\n): string[] {\n const limit = options.max ?? HASHTAG_LIMITS[platform] ?? 10;\n const banned = new Set([...DEFAULT_BANNED, ...(options.banned ?? []).map((b) => b.toLowerCase().replace(/^#/, \"\"))]);\n const seen = new Set<string>();\n const out: string[] = [];\n for (const raw of [...terms, ...(options.extra ?? [])]) {\n const tag = toHashtag(raw);\n const bare = tag.slice(1).toLowerCase();\n if (!tag || bare.length < 2 || banned.has(bare) || seen.has(bare)) continue;\n seen.add(bare);\n out.push(tag);\n if (out.length >= limit) break;\n }\n return out;\n}\n\nexport interface TrendsOptions {\n geo?: string;\n /** Sources to query. Default: google + wikipedia (youtube added iff youtubeApiKey). */\n sources?: TrendSource[];\n youtubeApiKey?: string;\n wikipediaProject?: string;\n /** Attach + filter by relevance to this story. */\n relevanceTo?: string;\n minRelevance?: number;\n fetch?: FetchLike;\n signal?: AbortSignal;\n}\n\nexport interface TrendsResult {\n trends: Trend[];\n sources: TrendSource[];\n warnings: string[];\n}\n\n/** Fetch, merge and score trends from the configured official/public sources. */\nexport async function trends(options: TrendsOptions = {}): Promise<TrendsResult> {\n const want = options.sources ?? ([\"google\", \"wikipedia\", ...(options.youtubeApiKey ? [\"youtube\"] as const : [])] as TrendSource[]);\n const warnings: string[] = [];\n const used: TrendSource[] = [];\n const raws: RawTrend[] = [];\n const run = async (s: TrendSource, fn: () => Promise<RawTrend[]>) => {\n try {\n const r = await fn();\n raws.push(...r);\n used.push(s);\n } catch (e) {\n warnings.push(`${s}: ${(e as Error).message}`);\n }\n };\n await Promise.all(\n want.map((s) => {\n if (s === \"google\") return run(\"google\", () => googleTrendsDaily({ geo: options.geo, fetch: options.fetch, signal: options.signal }));\n if (s === \"wikipedia\") return run(\"wikipedia\", () => wikipediaTop({ project: options.wikipediaProject, fetch: options.fetch, signal: options.signal }));\n if (s === \"youtube\" && options.youtubeApiKey) return run(\"youtube\", () => youtubeMostPopular({ apiKey: options.youtubeApiKey!, regionCode: options.geo, fetch: options.fetch, signal: options.signal }));\n return Promise.resolve();\n }),\n );\n let merged = mergeTrends(raws);\n if (options.relevanceTo) merged = relevanceTo(merged, options.relevanceTo, { min: options.minRelevance });\n return { trends: merged, sources: used, warnings };\n}\n\n/** Machine-readable descriptor for an AI \"conductor\". */\nexport function describe() {\n return {\n name: \"@lacspace/trends\",\n version: VERSION,\n summary: \"Trending topics for a region from official/public sources only (Google Trends RSS, YouTube mostPopular, Wikipedia pageviews), merged + scored, with story-relevance gating and per-platform hashtag sets.\",\n sources: [\"google\", \"youtube\", \"wikipedia\"],\n commands: [\n { name: \"trends\", input: { type: \"object\", properties: { geo: { type: \"string\" }, sources: { type: \"array\" }, youtubeApiKey: { type: \"string\" }, relevanceTo: { type: \"string\" }, minRelevance: { type: \"number\" } } }, output: \"{ trends:[{term,score,sources,volume,links,relevance}], sources, warnings }\" },\n { name: \"relevanceTo\", input: { type: \"object\", properties: { trends: { type: \"array\" }, story: { type: \"string\" }, min: { type: \"number\" } }, required: [\"trends\", \"story\"] }, output: \"Trend[] (with relevance)\" },\n { name: \"hashtagsFor\", input: { type: \"object\", properties: { terms: { type: \"array\" }, platform: { type: \"string\" }, max: { type: \"integer\" }, banned: { type: \"array\" } }, required: [\"terms\", \"platform\"] }, output: \"string[]\" },\n ],\n };\n}\n\nfunction round(n: number): number {\n return Math.round(n * 1000) / 1000;\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -53,7 +53,19 @@ interface Trend {
53
53
  }
54
54
  /** Merge raw trends from several sources into one scored, de-duplicated list. */
55
55
  declare function mergeTrends(raws: RawTrend[]): Trend[];
56
- /** Score each trend's relevance to a story (0–1) and attach it; optionally filter. */
56
+ /**
57
+ * Everyday nouns/verbs that are too generic to make a trend relevant on their own
58
+ * ("river" must not pull in "River Phoenix"). Stop-words come from @lacspace/keyphrase.
59
+ */
60
+ declare const COMMON_TERMS: string[];
61
+ /**
62
+ * Score each trend's relevance to a story (0–1) and attach it; optionally filter.
63
+ *
64
+ * A shared common word is never enough: a trend counts only when it matches the story on a
65
+ * distinctive term (a proper noun / entity / rare word) or as a whole phrase. Name-like trends
66
+ * ("River Phoenix") need every word of the name in the story. A single common-word trend
67
+ * ("Flood") is capped at 0.2.
68
+ */
57
69
  declare function relevanceTo(trends: Trend[], story: string, options?: {
58
70
  min?: number;
59
71
  }): Trend[];
@@ -179,4 +191,4 @@ declare function describe(): {
179
191
  })[];
180
192
  };
181
193
 
182
- export { type FetchLike, HASHTAG_LIMITS, type RawTrend, type Trend, type TrendSource, type TrendsOptions, type TrendsResult, describe, googleTrendsDaily, hashtagsFor, mergeTrends, parseGoogleTrendsRss, relevanceTo, trends, wikipediaTop, youtubeMostPopular };
194
+ export { COMMON_TERMS, type FetchLike, HASHTAG_LIMITS, type RawTrend, type Trend, type TrendSource, type TrendsOptions, type TrendsResult, describe, googleTrendsDaily, hashtagsFor, mergeTrends, parseGoogleTrendsRss, relevanceTo, trends, wikipediaTop, youtubeMostPopular };
package/dist/index.d.ts CHANGED
@@ -53,7 +53,19 @@ interface Trend {
53
53
  }
54
54
  /** Merge raw trends from several sources into one scored, de-duplicated list. */
55
55
  declare function mergeTrends(raws: RawTrend[]): Trend[];
56
- /** Score each trend's relevance to a story (0–1) and attach it; optionally filter. */
56
+ /**
57
+ * Everyday nouns/verbs that are too generic to make a trend relevant on their own
58
+ * ("river" must not pull in "River Phoenix"). Stop-words come from @lacspace/keyphrase.
59
+ */
60
+ declare const COMMON_TERMS: string[];
61
+ /**
62
+ * Score each trend's relevance to a story (0–1) and attach it; optionally filter.
63
+ *
64
+ * A shared common word is never enough: a trend counts only when it matches the story on a
65
+ * distinctive term (a proper noun / entity / rare word) or as a whole phrase. Name-like trends
66
+ * ("River Phoenix") need every word of the name in the story. A single common-word trend
67
+ * ("Flood") is capped at 0.2.
68
+ */
57
69
  declare function relevanceTo(trends: Trend[], story: string, options?: {
58
70
  min?: number;
59
71
  }): Trend[];
@@ -179,4 +191,4 @@ declare function describe(): {
179
191
  })[];
180
192
  };
181
193
 
182
- export { type FetchLike, HASHTAG_LIMITS, type RawTrend, type Trend, type TrendSource, type TrendsOptions, type TrendsResult, describe, googleTrendsDaily, hashtagsFor, mergeTrends, parseGoogleTrendsRss, relevanceTo, trends, wikipediaTop, youtubeMostPopular };
194
+ export { COMMON_TERMS, type FetchLike, HASHTAG_LIMITS, type RawTrend, type Trend, type TrendSource, type TrendsOptions, type TrendsResult, describe, googleTrendsDaily, hashtagsFor, mergeTrends, parseGoogleTrendsRss, relevanceTo, trends, wikipediaTop, youtubeMostPopular };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { keyphrase } from '@lacspace/keyphrase';
1
+ import { ENGLISH_STOPWORDS, keyphrase } from '@lacspace/keyphrase';
2
2
  import { detectScript, transliterate } from '@lacspace/translit';
3
3
 
4
4
  // src/index.ts
@@ -88,7 +88,7 @@ function decode(s) {
88
88
  }
89
89
 
90
90
  // src/index.ts
91
- var VERSION = "1.0.0";
91
+ var VERSION = "1.1.0";
92
92
  function keyOf(term) {
93
93
  const roman = detectScript(term) === "ne" ? transliterate(term, { from: "ne", to: "en" }) : term;
94
94
  return roman.toLowerCase().replace(/[^a-z0-9]+/g, "");
@@ -126,17 +126,34 @@ function mergeTrends(raws) {
126
126
  }
127
127
  return out.sort((a, b) => b.score - a.score);
128
128
  }
129
+ var COMMON_TERMS = "river lake water rain flood floods storm fire earthquake road bridge city town village country nation state world people man woman men women child children family home house school college university hospital market price prices money bank job jobs work worker news report story video photo live today tomorrow yesterday week month year day night time new old big small high low first last top best good bad open close update government minister ministry police army court law election vote party leader president prime team match game cup league final season player fans film movie song music show star stars love life death dead killed kill dies died attack crash accident case cases test result results index share shares stock stocks gold oil fuel power energy weather heat cold snow wind air sky sun moon park forest mountain hill valley animal animals bird birds dog cat tiger rhino rhinos elephant phone app online internet data tech car bus train plane flight airport festival holiday day nepal india china usa america global international national local district province area region zone north south east west red blue black white green golden king queen prince lord saint new big".split(/\s+/);
130
+ var COMMON = /* @__PURE__ */ new Set([...COMMON_TERMS, ...ENGLISH_STOPWORDS.map((w) => w.toLowerCase())]);
131
+ function stem(w) {
132
+ if (w.length <= 4) return w;
133
+ return w.replace(/(?:ing|ed|es|s)$/, "") || w;
134
+ }
135
+ var isCommon = (w) => COMMON.has(w) || COMMON.has(stem(w));
136
+ var nameLike = (term) => /^\p{Lu}[\p{Ll}'.-]+(?:\s+\p{Lu}[\p{Ll}'.-]+){1,2}$/u.test(term.trim());
129
137
  function relevanceTo(trends2, story, options = {}) {
130
138
  const kp = keyphrase(story, { max: 20 });
131
139
  const storyTokens = /* @__PURE__ */ new Set();
132
- for (const p of kp.phrases ?? []) for (const t of tokens(p.phrase)) storyTokens.add(t);
133
- for (const t of tokens(story)) storyTokens.add(t);
140
+ for (const p of kp.phrases ?? []) for (const t of tokens(p.phrase)) storyTokens.add(stem(t));
141
+ for (const t of tokens(story)) storyTokens.add(stem(t));
142
+ const storyKey = " " + tokens(story).map(stem).join(" ") + " ";
134
143
  const scored = trends2.map((t) => {
135
144
  const tt = tokens(t.term);
136
145
  if (!tt.length || storyTokens.size === 0) return { ...t, relevance: 0 };
137
- const hits = tt.filter((w) => storyTokens.has(w)).length;
138
- const rel = round(hits / tt.length);
139
- return { ...t, relevance: rel };
146
+ const has = (w) => storyTokens.has(stem(w));
147
+ const hits = tt.filter(has).length;
148
+ const phrase = tt.length > 1 && storyKey.includes(" " + tt.map(stem).join(" ") + " ");
149
+ const distinctive = tt.filter((w) => !isCommon(w));
150
+ let rel;
151
+ if (phrase) rel = 1;
152
+ else if (tt.length === 1) rel = hits ? distinctive.length ? 1 : 0.2 : 0;
153
+ else if (nameLike(t.term)) rel = hits === tt.length ? 1 : 0;
154
+ else if (!distinctive.length) rel = Math.min(0.2, hits / tt.length);
155
+ else rel = distinctive.every(has) ? hits / tt.length : 0;
156
+ return { ...t, relevance: round(rel) };
140
157
  });
141
158
  return options.min !== void 0 ? scored.filter((t) => (t.relevance ?? 0) >= options.min) : scored;
142
159
  }
@@ -214,6 +231,6 @@ function round(n) {
214
231
  return Math.round(n * 1e3) / 1e3;
215
232
  }
216
233
 
217
- export { HASHTAG_LIMITS, describe, googleTrendsDaily, hashtagsFor, mergeTrends, parseGoogleTrendsRss, relevanceTo, trends, wikipediaTop, youtubeMostPopular };
234
+ export { COMMON_TERMS, HASHTAG_LIMITS, describe, googleTrendsDaily, hashtagsFor, mergeTrends, parseGoogleTrendsRss, relevanceTo, trends, wikipediaTop, youtubeMostPopular };
218
235
  //# sourceMappingURL=index.js.map
219
236
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/sources.ts","../src/index.ts"],"names":["trends"],"mappings":"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","file":"index.js","sourcesContent":["export type TrendSource = \"google\" | \"youtube\" | \"wikipedia\";\n\nexport interface RawTrend {\n term: string;\n source: TrendSource;\n /** 0–1 weight within this source (1 = top). */\n weight: number;\n /** Approx search traffic / views, when the source gives it. */\n volume?: number;\n /** Supporting links (news items / article). */\n links?: string[];\n}\n\nexport type FetchLike = (url: string, init?: { headers?: Record<string, string>; signal?: AbortSignal }) => Promise<{ ok: boolean; status: number; text(): Promise<string>; json(): Promise<any> }>;\n\nfunction fetchImpl(f?: FetchLike): FetchLike {\n const impl = f ?? (globalThis as any).fetch;\n if (!impl) throw new Error(\"trends: no fetch available — pass options.fetch\");\n return impl;\n}\n\nfunction rankWeights(n: number): number[] {\n // Linear decay from 1 (rank 1) to ~0.1 (rank n).\n return Array.from({ length: n }, (_, i) => (n <= 1 ? 1 : 1 - (0.9 * i) / (n - 1)));\n}\n\nfunction parseTraffic(s: string | undefined): number | undefined {\n if (!s) return undefined;\n const m = s.replace(/,/g, \"\").match(/(\\d+)\\s*(\\+)?/);\n return m ? Number(m[1]) : undefined;\n}\n\n// --- Google Trends daily RSS (geo=NP by default) -----------------------------------\n/** Fetch + parse the Google Trends \"daily trending searches\" RSS for a region. */\nexport async function googleTrendsDaily(options: { geo?: string; fetch?: FetchLike; signal?: AbortSignal } = {}): Promise<RawTrend[]> {\n const geo = options.geo ?? \"NP\";\n const url = `https://trends.google.com/trends/trendingsearches/daily/rss?geo=${encodeURIComponent(geo)}`;\n const res = await fetchImpl(options.fetch)(url, { signal: options.signal });\n if (!res.ok) throw new Error(`google-trends ${res.status}`);\n return parseGoogleTrendsRss(await res.text());\n}\n\n/** Parse Google Trends daily RSS (title + ht:approx_traffic + ht:news_item links). */\nexport function parseGoogleTrendsRss(xml: string): RawTrend[] {\n const items = [...xml.matchAll(/<item>([\\s\\S]*?)<\\/item>/g)].map((m) => m[1]!);\n const out: { term: string; volume?: number; links: string[] }[] = [];\n for (const item of items) {\n const title = decode(first(item, /<title>([\\s\\S]*?)<\\/title>/));\n if (!title) continue;\n const volume = parseTraffic(first(item, /<ht:approx_traffic>([\\s\\S]*?)<\\/ht:approx_traffic>/));\n const links = [...item.matchAll(/<ht:news_item_url>([\\s\\S]*?)<\\/ht:news_item_url>/g)].map((m) => decode(m[1]!));\n out.push({ term: title, volume, links });\n }\n const w = rankWeights(out.length);\n return out.map((o, i) => ({ term: o.term, source: \"google\", weight: w[i]!, volume: o.volume, links: o.links }));\n}\n\n// --- YouTube Data API v3: most popular in a region (needs an API key) --------------\nexport async function youtubeMostPopular(options: { apiKey: string; regionCode?: string; maxResults?: number; fetch?: FetchLike; signal?: AbortSignal }): Promise<RawTrend[]> {\n if (!options.apiKey) return [];\n const u = new URL(\"https://www.googleapis.com/youtube/v3/videos\");\n u.searchParams.set(\"part\", \"snippet,statistics\");\n u.searchParams.set(\"chart\", \"mostPopular\");\n u.searchParams.set(\"regionCode\", options.regionCode ?? \"NP\");\n u.searchParams.set(\"maxResults\", String(options.maxResults ?? 25));\n u.searchParams.set(\"key\", options.apiKey);\n const res = await fetchImpl(options.fetch)(u.toString(), { signal: options.signal });\n if (!res.ok) throw new Error(`youtube ${res.status}`);\n const data = await res.json();\n const rows = (data.items ?? []) as any[];\n const w = rankWeights(rows.length);\n return rows.map((it, i) => ({\n term: it.snippet?.title ?? \"\",\n source: \"youtube\" as const,\n weight: w[i]!,\n volume: it.statistics?.viewCount ? Number(it.statistics.viewCount) : undefined,\n links: it.id ? [`https://youtu.be/${it.id}`] : [],\n })).filter((t) => t.term);\n}\n\n// --- Wikipedia pageviews top (free REST API, no key) -------------------------------\nexport async function wikipediaTop(options: { project?: string; date?: Date; fetch?: FetchLike; signal?: AbortSignal } = {}): Promise<RawTrend[]> {\n const project = options.project ?? \"en.wikipedia\";\n const d = options.date ?? new Date(Date.now() - 86400000); // yesterday (today may be incomplete)\n const yyyy = d.getUTCFullYear();\n const mm = String(d.getUTCMonth() + 1).padStart(2, \"0\");\n const dd = String(d.getUTCDate()).padStart(2, \"0\");\n const url = `https://wikimedia.org/api/rest_v1/metrics/pageviews/top/${project}/all-access/${yyyy}/${mm}/${dd}`;\n const res = await fetchImpl(options.fetch)(url, { headers: { \"User-Agent\": \"lacspace-trends/1.0 (contact@lacspace.com)\" }, signal: options.signal });\n if (!res.ok) throw new Error(`wikipedia ${res.status}`);\n const data = await res.json();\n const articles = (data.items?.[0]?.articles ?? []) as any[];\n const filtered = articles.filter((a) => !/^(Main_Page|Special:|Wikipedia:|विशेष:|मुख्य_पृष्ठ)/.test(a.article));\n const w = rankWeights(filtered.length);\n return filtered.map((a, i) => ({\n term: String(a.article).replace(/_/g, \" \"),\n source: \"wikipedia\" as const,\n weight: w[i]!,\n volume: a.views,\n links: [`https://${project}.org/wiki/${encodeURIComponent(a.article)}`],\n }));\n}\n\nfunction first(s: string, re: RegExp): string | undefined {\n const m = s.match(re);\n return m ? m[1]!.trim() : undefined;\n}\nfunction decode(s: string | undefined): string {\n return (s ?? \"\")\n .replace(/<!\\[CDATA\\[([\\s\\S]*?)\\]\\]>/g, \"$1\")\n .replace(/&amp;/g, \"&\").replace(/&lt;/g, \"<\").replace(/&gt;/g, \">\").replace(/&quot;/g, '\"').replace(/&#39;/g, \"'\")\n .trim();\n}\n","import { keyphrase } from \"@lacspace/keyphrase\";\nimport { transliterate, detectScript } from \"@lacspace/translit\";\nimport { FetchLike, googleTrendsDaily, RawTrend, TrendSource, wikipediaTop, youtubeMostPopular } from \"./sources.js\";\n\nexport * from \"./sources.js\";\n\nconst VERSION = \"1.0.0\";\n\nexport interface Trend {\n /** Display term (original casing/script of the strongest source). */\n term: string;\n /** Merged score, higher = more trending (multi-source terms rank up). */\n score: number;\n sources: TrendSource[];\n volume?: number;\n links: string[];\n /** 0–1 relevance to a story, set by relevanceTo / trends({ relevanceTo }). */\n relevance?: number;\n}\n\n/** A cross-script, lowercased key so \"Dashain\" and \"दशैं\" merge. */\nfunction keyOf(term: string): string {\n const roman = detectScript(term) === \"ne\" ? transliterate(term, { from: \"ne\", to: \"en\" }) : term;\n return roman.toLowerCase().replace(/[^a-z0-9]+/g, \"\");\n}\n\nfunction tokens(s: string): string[] {\n const roman = detectScript(s) === \"ne\" ? transliterate(s, { from: \"ne\", to: \"en\" }) : s;\n return (roman.toLowerCase().match(/[a-z0-9]+/g) ?? []).filter((w) => w.length > 2);\n}\n\n/** Merge raw trends from several sources into one scored, de-duplicated list. */\nexport function mergeTrends(raws: RawTrend[]): Trend[] {\n const groups = new Map<string, { best: RawTrend; score: number; sources: Set<TrendSource>; links: Set<string>; volume: number }>();\n for (const r of raws) {\n const k = keyOf(r.term);\n if (!k) continue;\n let g = groups.get(k);\n if (!g) {\n g = { best: r, score: 0, sources: new Set(), links: new Set(), volume: 0 };\n groups.set(k, g);\n }\n g.score += r.weight;\n g.sources.add(r.source);\n for (const l of r.links ?? []) g.links.add(l);\n if (r.volume) g.volume = Math.max(g.volume, r.volume);\n if (r.weight > g.best.weight) g.best = r; // strongest source sets the display term\n }\n const out: Trend[] = [];\n for (const g of groups.values()) {\n // Multi-source agreement is a strong signal → multiply by a diversity boost.\n const boost = 1 + 0.5 * (g.sources.size - 1);\n out.push({\n term: g.best.term,\n score: round(g.score * boost),\n sources: [...g.sources],\n volume: g.volume || undefined,\n links: [...g.links].slice(0, 5),\n });\n }\n return out.sort((a, b) => b.score - a.score);\n}\n\n/** Score each trend's relevance to a story (0–1) and attach it; optionally filter. */\nexport function relevanceTo(trends: Trend[], story: string, options: { min?: number } = {}): Trend[] {\n const kp = keyphrase(story, { max: 20 } as any);\n const storyTokens = new Set<string>();\n for (const p of kp.phrases ?? []) for (const t of tokens(p.phrase)) storyTokens.add(t);\n for (const t of tokens(story)) storyTokens.add(t);\n const scored = trends.map((t) => {\n const tt = tokens(t.term);\n if (!tt.length || storyTokens.size === 0) return { ...t, relevance: 0 };\n const hits = tt.filter((w) => storyTokens.has(w)).length;\n const rel = round(hits / tt.length);\n return { ...t, relevance: rel };\n });\n return options.min !== undefined ? scored.filter((t) => (t.relevance ?? 0) >= options.min!) : scored;\n}\n\n// --- hashtags ----------------------------------------------------------------------\nexport const HASHTAG_LIMITS: Record<string, number> = {\n instagram: 30, tiktok: 6, x: 3, facebook: 3, youtube: 15, threads: 5, linkedin: 5, telegram: 10,\n};\n// A conservative starter list of broken/shadow-ban-prone tags; callers extend.\nconst DEFAULT_BANNED = new Set([\"followme\", \"follow4follow\", \"like4like\", \"nepaltiktok\", \"viral\", \"fyp\"]);\n\nfunction toHashtag(term: string): string {\n const roman = detectScript(term) === \"ne\" ? transliterate(term, { from: \"ne\", to: \"en\" }) : term;\n const camel = roman.replace(/[^a-zA-Z0-9]+/g, \" \").trim().split(/\\s+/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(\"\");\n return camel ? \"#\" + camel : \"\";\n}\n\n/** Build a platform-appropriate hashtag set from trend terms (+ optional extra tags). */\nexport function hashtagsFor(\n terms: string[],\n platform: keyof typeof HASHTAG_LIMITS | string,\n options: { banned?: string[]; max?: number; extra?: string[] } = {},\n): string[] {\n const limit = options.max ?? HASHTAG_LIMITS[platform] ?? 10;\n const banned = new Set([...DEFAULT_BANNED, ...(options.banned ?? []).map((b) => b.toLowerCase().replace(/^#/, \"\"))]);\n const seen = new Set<string>();\n const out: string[] = [];\n for (const raw of [...terms, ...(options.extra ?? [])]) {\n const tag = toHashtag(raw);\n const bare = tag.slice(1).toLowerCase();\n if (!tag || bare.length < 2 || banned.has(bare) || seen.has(bare)) continue;\n seen.add(bare);\n out.push(tag);\n if (out.length >= limit) break;\n }\n return out;\n}\n\nexport interface TrendsOptions {\n geo?: string;\n /** Sources to query. Default: google + wikipedia (youtube added iff youtubeApiKey). */\n sources?: TrendSource[];\n youtubeApiKey?: string;\n wikipediaProject?: string;\n /** Attach + filter by relevance to this story. */\n relevanceTo?: string;\n minRelevance?: number;\n fetch?: FetchLike;\n signal?: AbortSignal;\n}\n\nexport interface TrendsResult {\n trends: Trend[];\n sources: TrendSource[];\n warnings: string[];\n}\n\n/** Fetch, merge and score trends from the configured official/public sources. */\nexport async function trends(options: TrendsOptions = {}): Promise<TrendsResult> {\n const want = options.sources ?? ([\"google\", \"wikipedia\", ...(options.youtubeApiKey ? [\"youtube\"] as const : [])] as TrendSource[]);\n const warnings: string[] = [];\n const used: TrendSource[] = [];\n const raws: RawTrend[] = [];\n const run = async (s: TrendSource, fn: () => Promise<RawTrend[]>) => {\n try {\n const r = await fn();\n raws.push(...r);\n used.push(s);\n } catch (e) {\n warnings.push(`${s}: ${(e as Error).message}`);\n }\n };\n await Promise.all(\n want.map((s) => {\n if (s === \"google\") return run(\"google\", () => googleTrendsDaily({ geo: options.geo, fetch: options.fetch, signal: options.signal }));\n if (s === \"wikipedia\") return run(\"wikipedia\", () => wikipediaTop({ project: options.wikipediaProject, fetch: options.fetch, signal: options.signal }));\n if (s === \"youtube\" && options.youtubeApiKey) return run(\"youtube\", () => youtubeMostPopular({ apiKey: options.youtubeApiKey!, regionCode: options.geo, fetch: options.fetch, signal: options.signal }));\n return Promise.resolve();\n }),\n );\n let merged = mergeTrends(raws);\n if (options.relevanceTo) merged = relevanceTo(merged, options.relevanceTo, { min: options.minRelevance });\n return { trends: merged, sources: used, warnings };\n}\n\n/** Machine-readable descriptor for an AI \"conductor\". */\nexport function describe() {\n return {\n name: \"@lacspace/trends\",\n version: VERSION,\n summary: \"Trending topics for a region from official/public sources only (Google Trends RSS, YouTube mostPopular, Wikipedia pageviews), merged + scored, with story-relevance gating and per-platform hashtag sets.\",\n sources: [\"google\", \"youtube\", \"wikipedia\"],\n commands: [\n { name: \"trends\", input: { type: \"object\", properties: { geo: { type: \"string\" }, sources: { type: \"array\" }, youtubeApiKey: { type: \"string\" }, relevanceTo: { type: \"string\" }, minRelevance: { type: \"number\" } } }, output: \"{ trends:[{term,score,sources,volume,links,relevance}], sources, warnings }\" },\n { name: \"relevanceTo\", input: { type: \"object\", properties: { trends: { type: \"array\" }, story: { type: \"string\" }, min: { type: \"number\" } }, required: [\"trends\", \"story\"] }, output: \"Trend[] (with relevance)\" },\n { name: \"hashtagsFor\", input: { type: \"object\", properties: { terms: { type: \"array\" }, platform: { type: \"string\" }, max: { type: \"integer\" }, banned: { type: \"array\" } }, required: [\"terms\", \"platform\"] }, output: \"string[]\" },\n ],\n };\n}\n\nfunction round(n: number): number {\n return Math.round(n * 1000) / 1000;\n}\n"]}
1
+ {"version":3,"sources":["../src/sources.ts","../src/index.ts"],"names":["trends"],"mappings":"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","file":"index.js","sourcesContent":["export type TrendSource = \"google\" | \"youtube\" | \"wikipedia\";\n\nexport interface RawTrend {\n term: string;\n source: TrendSource;\n /** 0–1 weight within this source (1 = top). */\n weight: number;\n /** Approx search traffic / views, when the source gives it. */\n volume?: number;\n /** Supporting links (news items / article). */\n links?: string[];\n}\n\nexport type FetchLike = (url: string, init?: { headers?: Record<string, string>; signal?: AbortSignal }) => Promise<{ ok: boolean; status: number; text(): Promise<string>; json(): Promise<any> }>;\n\nfunction fetchImpl(f?: FetchLike): FetchLike {\n const impl = f ?? (globalThis as any).fetch;\n if (!impl) throw new Error(\"trends: no fetch available — pass options.fetch\");\n return impl;\n}\n\nfunction rankWeights(n: number): number[] {\n // Linear decay from 1 (rank 1) to ~0.1 (rank n).\n return Array.from({ length: n }, (_, i) => (n <= 1 ? 1 : 1 - (0.9 * i) / (n - 1)));\n}\n\nfunction parseTraffic(s: string | undefined): number | undefined {\n if (!s) return undefined;\n const m = s.replace(/,/g, \"\").match(/(\\d+)\\s*(\\+)?/);\n return m ? Number(m[1]) : undefined;\n}\n\n// --- Google Trends daily RSS (geo=NP by default) -----------------------------------\n/** Fetch + parse the Google Trends \"daily trending searches\" RSS for a region. */\nexport async function googleTrendsDaily(options: { geo?: string; fetch?: FetchLike; signal?: AbortSignal } = {}): Promise<RawTrend[]> {\n const geo = options.geo ?? \"NP\";\n const url = `https://trends.google.com/trends/trendingsearches/daily/rss?geo=${encodeURIComponent(geo)}`;\n const res = await fetchImpl(options.fetch)(url, { signal: options.signal });\n if (!res.ok) throw new Error(`google-trends ${res.status}`);\n return parseGoogleTrendsRss(await res.text());\n}\n\n/** Parse Google Trends daily RSS (title + ht:approx_traffic + ht:news_item links). */\nexport function parseGoogleTrendsRss(xml: string): RawTrend[] {\n const items = [...xml.matchAll(/<item>([\\s\\S]*?)<\\/item>/g)].map((m) => m[1]!);\n const out: { term: string; volume?: number; links: string[] }[] = [];\n for (const item of items) {\n const title = decode(first(item, /<title>([\\s\\S]*?)<\\/title>/));\n if (!title) continue;\n const volume = parseTraffic(first(item, /<ht:approx_traffic>([\\s\\S]*?)<\\/ht:approx_traffic>/));\n const links = [...item.matchAll(/<ht:news_item_url>([\\s\\S]*?)<\\/ht:news_item_url>/g)].map((m) => decode(m[1]!));\n out.push({ term: title, volume, links });\n }\n const w = rankWeights(out.length);\n return out.map((o, i) => ({ term: o.term, source: \"google\", weight: w[i]!, volume: o.volume, links: o.links }));\n}\n\n// --- YouTube Data API v3: most popular in a region (needs an API key) --------------\nexport async function youtubeMostPopular(options: { apiKey: string; regionCode?: string; maxResults?: number; fetch?: FetchLike; signal?: AbortSignal }): Promise<RawTrend[]> {\n if (!options.apiKey) return [];\n const u = new URL(\"https://www.googleapis.com/youtube/v3/videos\");\n u.searchParams.set(\"part\", \"snippet,statistics\");\n u.searchParams.set(\"chart\", \"mostPopular\");\n u.searchParams.set(\"regionCode\", options.regionCode ?? \"NP\");\n u.searchParams.set(\"maxResults\", String(options.maxResults ?? 25));\n u.searchParams.set(\"key\", options.apiKey);\n const res = await fetchImpl(options.fetch)(u.toString(), { signal: options.signal });\n if (!res.ok) throw new Error(`youtube ${res.status}`);\n const data = await res.json();\n const rows = (data.items ?? []) as any[];\n const w = rankWeights(rows.length);\n return rows.map((it, i) => ({\n term: it.snippet?.title ?? \"\",\n source: \"youtube\" as const,\n weight: w[i]!,\n volume: it.statistics?.viewCount ? Number(it.statistics.viewCount) : undefined,\n links: it.id ? [`https://youtu.be/${it.id}`] : [],\n })).filter((t) => t.term);\n}\n\n// --- Wikipedia pageviews top (free REST API, no key) -------------------------------\nexport async function wikipediaTop(options: { project?: string; date?: Date; fetch?: FetchLike; signal?: AbortSignal } = {}): Promise<RawTrend[]> {\n const project = options.project ?? \"en.wikipedia\";\n const d = options.date ?? new Date(Date.now() - 86400000); // yesterday (today may be incomplete)\n const yyyy = d.getUTCFullYear();\n const mm = String(d.getUTCMonth() + 1).padStart(2, \"0\");\n const dd = String(d.getUTCDate()).padStart(2, \"0\");\n const url = `https://wikimedia.org/api/rest_v1/metrics/pageviews/top/${project}/all-access/${yyyy}/${mm}/${dd}`;\n const res = await fetchImpl(options.fetch)(url, { headers: { \"User-Agent\": \"lacspace-trends/1.0 (contact@lacspace.com)\" }, signal: options.signal });\n if (!res.ok) throw new Error(`wikipedia ${res.status}`);\n const data = await res.json();\n const articles = (data.items?.[0]?.articles ?? []) as any[];\n const filtered = articles.filter((a) => !/^(Main_Page|Special:|Wikipedia:|विशेष:|मुख्य_पृष्ठ)/.test(a.article));\n const w = rankWeights(filtered.length);\n return filtered.map((a, i) => ({\n term: String(a.article).replace(/_/g, \" \"),\n source: \"wikipedia\" as const,\n weight: w[i]!,\n volume: a.views,\n links: [`https://${project}.org/wiki/${encodeURIComponent(a.article)}`],\n }));\n}\n\nfunction first(s: string, re: RegExp): string | undefined {\n const m = s.match(re);\n return m ? m[1]!.trim() : undefined;\n}\nfunction decode(s: string | undefined): string {\n return (s ?? \"\")\n .replace(/<!\\[CDATA\\[([\\s\\S]*?)\\]\\]>/g, \"$1\")\n .replace(/&amp;/g, \"&\").replace(/&lt;/g, \"<\").replace(/&gt;/g, \">\").replace(/&quot;/g, '\"').replace(/&#39;/g, \"'\")\n .trim();\n}\n","import { ENGLISH_STOPWORDS, keyphrase } from \"@lacspace/keyphrase\";\nimport { transliterate, detectScript } from \"@lacspace/translit\";\nimport { FetchLike, googleTrendsDaily, RawTrend, TrendSource, wikipediaTop, youtubeMostPopular } from \"./sources.js\";\n\nexport * from \"./sources.js\";\n\nconst VERSION = \"1.1.0\";\n\nexport interface Trend {\n /** Display term (original casing/script of the strongest source). */\n term: string;\n /** Merged score, higher = more trending (multi-source terms rank up). */\n score: number;\n sources: TrendSource[];\n volume?: number;\n links: string[];\n /** 0–1 relevance to a story, set by relevanceTo / trends({ relevanceTo }). */\n relevance?: number;\n}\n\n/** A cross-script, lowercased key so \"Dashain\" and \"दशैं\" merge. */\nfunction keyOf(term: string): string {\n const roman = detectScript(term) === \"ne\" ? transliterate(term, { from: \"ne\", to: \"en\" }) : term;\n return roman.toLowerCase().replace(/[^a-z0-9]+/g, \"\");\n}\n\nfunction tokens(s: string): string[] {\n const roman = detectScript(s) === \"ne\" ? transliterate(s, { from: \"ne\", to: \"en\" }) : s;\n return (roman.toLowerCase().match(/[a-z0-9]+/g) ?? []).filter((w) => w.length > 2);\n}\n\n/** Merge raw trends from several sources into one scored, de-duplicated list. */\nexport function mergeTrends(raws: RawTrend[]): Trend[] {\n const groups = new Map<string, { best: RawTrend; score: number; sources: Set<TrendSource>; links: Set<string>; volume: number }>();\n for (const r of raws) {\n const k = keyOf(r.term);\n if (!k) continue;\n let g = groups.get(k);\n if (!g) {\n g = { best: r, score: 0, sources: new Set(), links: new Set(), volume: 0 };\n groups.set(k, g);\n }\n g.score += r.weight;\n g.sources.add(r.source);\n for (const l of r.links ?? []) g.links.add(l);\n if (r.volume) g.volume = Math.max(g.volume, r.volume);\n if (r.weight > g.best.weight) g.best = r; // strongest source sets the display term\n }\n const out: Trend[] = [];\n for (const g of groups.values()) {\n // Multi-source agreement is a strong signal → multiply by a diversity boost.\n const boost = 1 + 0.5 * (g.sources.size - 1);\n out.push({\n term: g.best.term,\n score: round(g.score * boost),\n sources: [...g.sources],\n volume: g.volume || undefined,\n links: [...g.links].slice(0, 5),\n });\n }\n return out.sort((a, b) => b.score - a.score);\n}\n\n/**\n * Everyday nouns/verbs that are too generic to make a trend relevant on their own\n * (\"river\" must not pull in \"River Phoenix\"). Stop-words come from @lacspace/keyphrase.\n */\nexport const COMMON_TERMS: string[] = (\n \"river lake water rain flood floods storm fire earthquake road bridge city town village country nation state world people man woman men women \" +\n \"child children family home house school college university hospital market price prices money bank job jobs work worker news report story video \" +\n \"photo live today tomorrow yesterday week month year day night time new old big small high low first last top best good bad open close update \" +\n \"government minister ministry police army court law election vote party leader president prime team match game cup league final season player \" +\n \"fans film movie song music show star stars love life death dead killed kill dies died attack crash accident case cases test result results \" +\n \"index share shares stock stocks gold oil fuel power energy weather heat cold snow wind air sky sun moon park forest mountain hill valley \" +\n \"animal animals bird birds dog cat tiger rhino rhinos elephant phone app online internet data tech car bus train plane flight airport \" +\n \"festival holiday day nepal india china usa america global international national local district province area region zone north south east west \" +\n \"red blue black white green golden king queen prince lord saint new big\"\n).split(/\\s+/);\nconst COMMON = new Set([...COMMON_TERMS, ...ENGLISH_STOPWORDS.map((w) => w.toLowerCase())]);\n\n/** Crude English stem so \"floods\"/\"flooded\"/\"flooding\" meet \"flood\". */\nfunction stem(w: string): string {\n if (w.length <= 4) return w;\n return w.replace(/(?:ing|ed|es|s)$/, \"\") || w;\n}\nconst isCommon = (w: string) => COMMON.has(w) || COMMON.has(stem(w));\n/** Title-cased 2–3 Latin words, e.g. \"River Phoenix\" — treat as a possible person name. */\nconst nameLike = (term: string) => /^\\p{Lu}[\\p{Ll}'.-]+(?:\\s+\\p{Lu}[\\p{Ll}'.-]+){1,2}$/u.test(term.trim());\n\n/**\n * Score each trend's relevance to a story (0–1) and attach it; optionally filter.\n *\n * A shared common word is never enough: a trend counts only when it matches the story on a\n * distinctive term (a proper noun / entity / rare word) or as a whole phrase. Name-like trends\n * (\"River Phoenix\") need every word of the name in the story. A single common-word trend\n * (\"Flood\") is capped at 0.2.\n */\nexport function relevanceTo(trends: Trend[], story: string, options: { min?: number } = {}): Trend[] {\n const kp = keyphrase(story, { max: 20 } as any);\n const storyTokens = new Set<string>();\n for (const p of kp.phrases ?? []) for (const t of tokens(p.phrase)) storyTokens.add(stem(t));\n for (const t of tokens(story)) storyTokens.add(stem(t));\n const storyKey = \" \" + tokens(story).map(stem).join(\" \") + \" \";\n const scored = trends.map((t) => {\n const tt = tokens(t.term);\n if (!tt.length || storyTokens.size === 0) return { ...t, relevance: 0 };\n const has = (w: string) => storyTokens.has(stem(w));\n const hits = tt.filter(has).length;\n const phrase = tt.length > 1 && storyKey.includes(\" \" + tt.map(stem).join(\" \") + \" \");\n const distinctive = tt.filter((w) => !isCommon(w));\n let rel: number;\n if (phrase) rel = 1;\n else if (tt.length === 1) rel = hits ? (distinctive.length ? 1 : 0.2) : 0;\n else if (nameLike(t.term)) rel = hits === tt.length ? 1 : 0;\n else if (!distinctive.length) rel = Math.min(0.2, hits / tt.length);\n else rel = distinctive.every(has) ? hits / tt.length : 0;\n return { ...t, relevance: round(rel) };\n });\n return options.min !== undefined ? scored.filter((t) => (t.relevance ?? 0) >= options.min!) : scored;\n}\n\n// --- hashtags ----------------------------------------------------------------------\nexport const HASHTAG_LIMITS: Record<string, number> = {\n instagram: 30, tiktok: 6, x: 3, facebook: 3, youtube: 15, threads: 5, linkedin: 5, telegram: 10,\n};\n// A conservative starter list of broken/shadow-ban-prone tags; callers extend.\nconst DEFAULT_BANNED = new Set([\"followme\", \"follow4follow\", \"like4like\", \"nepaltiktok\", \"viral\", \"fyp\"]);\n\nfunction toHashtag(term: string): string {\n const roman = detectScript(term) === \"ne\" ? transliterate(term, { from: \"ne\", to: \"en\" }) : term;\n const camel = roman.replace(/[^a-zA-Z0-9]+/g, \" \").trim().split(/\\s+/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(\"\");\n return camel ? \"#\" + camel : \"\";\n}\n\n/** Build a platform-appropriate hashtag set from trend terms (+ optional extra tags). */\nexport function hashtagsFor(\n terms: string[],\n platform: keyof typeof HASHTAG_LIMITS | string,\n options: { banned?: string[]; max?: number; extra?: string[] } = {},\n): string[] {\n const limit = options.max ?? HASHTAG_LIMITS[platform] ?? 10;\n const banned = new Set([...DEFAULT_BANNED, ...(options.banned ?? []).map((b) => b.toLowerCase().replace(/^#/, \"\"))]);\n const seen = new Set<string>();\n const out: string[] = [];\n for (const raw of [...terms, ...(options.extra ?? [])]) {\n const tag = toHashtag(raw);\n const bare = tag.slice(1).toLowerCase();\n if (!tag || bare.length < 2 || banned.has(bare) || seen.has(bare)) continue;\n seen.add(bare);\n out.push(tag);\n if (out.length >= limit) break;\n }\n return out;\n}\n\nexport interface TrendsOptions {\n geo?: string;\n /** Sources to query. Default: google + wikipedia (youtube added iff youtubeApiKey). */\n sources?: TrendSource[];\n youtubeApiKey?: string;\n wikipediaProject?: string;\n /** Attach + filter by relevance to this story. */\n relevanceTo?: string;\n minRelevance?: number;\n fetch?: FetchLike;\n signal?: AbortSignal;\n}\n\nexport interface TrendsResult {\n trends: Trend[];\n sources: TrendSource[];\n warnings: string[];\n}\n\n/** Fetch, merge and score trends from the configured official/public sources. */\nexport async function trends(options: TrendsOptions = {}): Promise<TrendsResult> {\n const want = options.sources ?? ([\"google\", \"wikipedia\", ...(options.youtubeApiKey ? [\"youtube\"] as const : [])] as TrendSource[]);\n const warnings: string[] = [];\n const used: TrendSource[] = [];\n const raws: RawTrend[] = [];\n const run = async (s: TrendSource, fn: () => Promise<RawTrend[]>) => {\n try {\n const r = await fn();\n raws.push(...r);\n used.push(s);\n } catch (e) {\n warnings.push(`${s}: ${(e as Error).message}`);\n }\n };\n await Promise.all(\n want.map((s) => {\n if (s === \"google\") return run(\"google\", () => googleTrendsDaily({ geo: options.geo, fetch: options.fetch, signal: options.signal }));\n if (s === \"wikipedia\") return run(\"wikipedia\", () => wikipediaTop({ project: options.wikipediaProject, fetch: options.fetch, signal: options.signal }));\n if (s === \"youtube\" && options.youtubeApiKey) return run(\"youtube\", () => youtubeMostPopular({ apiKey: options.youtubeApiKey!, regionCode: options.geo, fetch: options.fetch, signal: options.signal }));\n return Promise.resolve();\n }),\n );\n let merged = mergeTrends(raws);\n if (options.relevanceTo) merged = relevanceTo(merged, options.relevanceTo, { min: options.minRelevance });\n return { trends: merged, sources: used, warnings };\n}\n\n/** Machine-readable descriptor for an AI \"conductor\". */\nexport function describe() {\n return {\n name: \"@lacspace/trends\",\n version: VERSION,\n summary: \"Trending topics for a region from official/public sources only (Google Trends RSS, YouTube mostPopular, Wikipedia pageviews), merged + scored, with story-relevance gating and per-platform hashtag sets.\",\n sources: [\"google\", \"youtube\", \"wikipedia\"],\n commands: [\n { name: \"trends\", input: { type: \"object\", properties: { geo: { type: \"string\" }, sources: { type: \"array\" }, youtubeApiKey: { type: \"string\" }, relevanceTo: { type: \"string\" }, minRelevance: { type: \"number\" } } }, output: \"{ trends:[{term,score,sources,volume,links,relevance}], sources, warnings }\" },\n { name: \"relevanceTo\", input: { type: \"object\", properties: { trends: { type: \"array\" }, story: { type: \"string\" }, min: { type: \"number\" } }, required: [\"trends\", \"story\"] }, output: \"Trend[] (with relevance)\" },\n { name: \"hashtagsFor\", input: { type: \"object\", properties: { terms: { type: \"array\" }, platform: { type: \"string\" }, max: { type: \"integer\" }, banned: { type: \"array\" } }, required: [\"terms\", \"platform\"] }, output: \"string[]\" },\n ],\n };\n}\n\nfunction round(n: number): number {\n return Math.round(n * 1000) / 1000;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lacspace/trends",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Trending topics and hashtags for a region from official/public sources only — Google Trends daily RSS (geo), YouTube Data API mostPopular (optional key), and Wikipedia pageviews — merged across scripts and scored (multi-source terms rank up), with story-relevance gating so you never trend-jack unrelated topics, and per-platform hashtag sets with limits + a banned/shadow-ban filter. Bring-your-own-fetch, deterministic given inputs. Reuses @lacspace/keyphrase and @lacspace/translit.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",