SLOPSHOPPER

trend-th

เทรนด์ล่าสุดจากแหล่งจริง: Google Trends ไทย, Hacker News, GitHub repo มาแรง, คริปโตมาแรง และ Wikipedia ที่คนอ่านมากสุด

newcommandtoastnetworktimer
★ 3v1.0.0MITupdated 2026-10-24tksoft11/claude-code-mods-th/mods/mod-38-trend
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · trend-th
› fix the failing auth test and add an audit log call ⏺ Read(src/auth.ts) ⎿ Read 6 lines ⏺ Update(src/auth.ts) ⎿ Added 2 lines, removed 1 line ⏺ Bash(bun test) ⎿ 3 pass, 1 fail ● Done. refresh now rejects expired claims and logs an audit event. ✻ Worked for 42s · done 4:20 PM › /trend ⎿ trend-th: 🇹🇭 Google ไทยวันนี้ ⎿ trend-th: ⎿ trend-th: 💻 Hacker News ⎿ trend-th: 1. (ดึงไม่ได้) ⎿ trend-th: ⎿ trend-th: ⭐ GitHub repo ใหม่ดาวพุ่ง (7 วัน) ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts
README

Mod 38: Trend TH

เทรนด์ล่าสุดจากแหล่งข้อมูลจริง: คำค้นมาแรงในไทยจาก Google Trends, ข่าว tech จาก Hacker News, repo ใหม่ที่ได้ดาวเร็วที่สุดบน GitHub, เหรียญคริปโตที่คนค้นมากสุด และบทความ Wikipedia ที่คนอ่านมากสุดเมื่อวาน ติดตามคำที่สนใจแล้วเด้งเตือนเมื่อคำนั้นขึ้นเทรนด์ได้

คำสั่ง

คำสั่งทำอะไร
/trendสรุปทุกแหล่ง
/trend google [ประเทศ]Google Trends (TH, US, JP ...)
/trend hnHacker News
/trend github [วัน] [ภาษา]repo ใหม่ดาวพุ่ง
/trend cryptoคริปโตมาแรง
`/trend wiki [th\en]`Wikipedia ที่คนอ่านมากสุด
/trend watch <คำ> / unwatch <คำ>ติดตามคำ

ติดตั้ง

/plugin install trend-th --marketplace tksoft11/claude-code-mods-th

ต้องตั้ง CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1 ก่อนเปิด claude (ดู README หลัก)

โค้ดสอนอะไร

เปิด hooks/register.tsx แล้วอ่านตามบทที่เขียนไว้ในคอมเมนต์:

  1. อ่าน RSS (XML) ของ Google Trends ด้วย regex
  2. อ่าน JSON จาก Hacker News, GitHub, CoinGecko, Wikimedia
  3. ติดตามคำด้วย timer ทุก 1 ชม.
  4. คำสั่ง /trend และดึงหลายแหล่งพร้อมกันโดยแหล่งที่พังไม่ทำให้แหล่งอื่นพังตาม

หมายเหตุ

GitHub API แบบไม่ใช้ token จำกัดจำนวนครั้งต่อชั่วโมง บทความ Wikipedia ที่คนอ่านมากสุดเป็นข้อมูลดิบจากผู้อ่าน อาจมีหัวข้อที่ไม่เหมาะสม Mod กรองหัวข้อสั้นเกินไปออกแล้วแต่ไม่ได้กรองทั้งหมด

Source 1 files
hooks/register.tsx 198 lines
1/**
2 * ============================================================================
3 * MOD 38: Trend TH - เทรนด์ล่าสุดจากแหล่งข้อมูลจริง (ฉบับสอนทำ)
4 * ============================================================================
5 * แรงบันดาลใจจาก: Google Trends, Hacker News, GitHub Trending
6 *
7 * หน้าที่ของ Mod นี้:
8 * 1. คำค้นมาแรงในไทยวันนี้ (Google Trends RSS) พร้อมจำนวนค้นหาโดยประมาณ
9 * 2. ข่าว tech ที่ Dev ทั่วโลกคุยกัน (Hacker News)
10 * 3. repo ใหม่ที่ได้ดาวเร็วที่สุด (GitHub search API)
11 * 4. เหรียญคริปโตที่คนค้นมากสุด (CoinGecko trending)
12 * 5. บทความ Wikipedia ไทย/อังกฤษที่คนอ่านมากสุดเมื่อวาน
13 * 6. ติดตามคำ (watch): เด้งเตือนเมื่อคำที่สนใจขึ้นเทรนด์ Google
14 */
15
16import type { EngineInterface, Register, Timer } from 'claude-code'
17
18const UA = { 'User-Agent': 'claude-code-trend-th/1.0 (educational mod)' }
19let watches: string[] = []
20let watchTimer: Timer | undefined
21let alerted = new Set<string>()
22
23async function get($: EngineInterface, url: string): Promise<string> {
24  const res = await $.http.fetch(url, { headers: UA })
25  if (!res.ok) throw new Error(`HTTP ${res.status}`)
26  return res.text
27}
28
29function decode(s: string): string {
30  return s.replace(/<!\[CDATA\[|\]\]>/g, '').replace(/&amp;/g, '&').replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&lt;/g, '<').replace(/&gt;/g, '>').trim()
31}
32
33// ==========================================================================
34// บทที่ 1: Google Trends - อ่าน RSS (XML) ด้วย regex
35// ==========================================================================
36interface GoogleTrend {
37  title: string
38  traffic: string
39  news: string
40}
41
42async function googleTrends($: EngineInterface, geo: string): Promise<GoogleTrend[]> {
43  const xml = await get($, `https://trends.google.com/trending/rss?geo=${encodeURIComponent(geo)}`)
44  return [...xml.matchAll(/<item>([\s\S]*?)<\/item>/g)].map(m => {
45    const item = m[1] ?? ''
46    const tag = (name: string) => decode(new RegExp(`<${name}>([\\s\\S]*?)</${name}>`).exec(item)?.[1] ?? '')
47    return { title: tag('title'), traffic: tag('ht:approx_traffic'), news: tag('ht:news_item_title') }
48  })
49}
50
51// ==========================================================================
52// บทที่ 2: Hacker News, GitHub, CoinGecko, Wikipedia (JSON ทั้งหมด)
53// ==========================================================================
54async function hackerNews($: EngineInterface, n: number): Promise<string[]> {
55  const ids = (JSON.parse(await get($, 'https://hacker-news.firebaseio.com/v0/topstories.json')) as number[]).slice(0, n)
56  const items = await Promise.all(ids.map(id => get($, `https://hacker-news.firebaseio.com/v0/item/${id}.json`).then(t => JSON.parse(t) as { title?: string; score?: number; descendants?: number; url?: string }).catch(() => undefined)))
57  return items.filter(Boolean).map(i => `▲${i?.score ?? 0} 💬${i?.descendants ?? 0}  ${i?.title ?? ''}${i?.url ? `\n      ${i.url}` : ''}`)
58}
59
60async function githubRising($: EngineInterface, days: number, language?: string): Promise<string[]> {
61  const since = new Date((await $.clock.now()) - days * 86_400_000).toISOString().slice(0, 10)
62  const q = `created:>${since}${language ? ` language:${language}` : ''}`
63  const body = JSON.parse(await get($, `https://api.github.com/search/repositories?q=${encodeURIComponent(q)}&sort=stars&order=desc&per_page=10`)) as { items?: { full_name: string; stargazers_count: number; description?: string | null; language?: string | null }[] }
64  return (body.items ?? []).map(r => `⭐${r.stargazers_count.toLocaleString('en-US')}  ${r.full_name}${r.language ? ` [${r.language}]` : ''}${r.description ? `\n      ${r.description.slice(0, 90)}` : ''}`)
65}
66
67async function cryptoTrending($: EngineInterface): Promise<string[]> {
68  const body = JSON.parse(await get($, 'https://api.coingecko.com/api/v3/search/trending')) as { coins?: { item: { name: string; symbol: string; market_cap_rank?: number | null } }[] }
69  return (body.coins ?? []).slice(0, 10).map(c => `${c.item.symbol.padEnd(8)} ${c.item.name}${c.item.market_cap_rank ? ` (อันดับ ${c.item.market_cap_rank})` : ''}`)
70}
71
72async function wikiMostRead($: EngineInterface, lang: string): Promise<string[]> {
73  const y = new Date((await $.clock.now()) - 86_400_000) // ข้อมูลของเมื่อวาน (วันนี้ยังนับไม่จบ)
74  const path = `${y.getFullYear()}/${String(y.getMonth() + 1).padStart(2, '0')}/${String(y.getDate()).padStart(2, '0')}`
75  const body = JSON.parse(await get($, `https://api.wikimedia.org/feed/v1/wikipedia/${lang}/featured/${path}`)) as { mostread?: { articles?: { normalizedtitle: string; views: number; description?: string }[] } }
76  return (body.mostread?.articles ?? [])
77    // ตัดหน้าระบบ และหัวข้อสั้นเกินไป (มักเป็นคำหยาบหรือหน้าที่ไม่มีเนื้อหา)
78    .filter(a => !/^(Main Page|หน้าหลัก|Special:|พิเศษ:)/.test(a.normalizedtitle) && [...a.normalizedtitle].length > 2)
79    .slice(0, 10)
80    .map(a => `👁️${a.views.toLocaleString('en-US')}  ${a.normalizedtitle}${a.description ? ` - ${a.description.slice(0, 60)}` : ''}`)
81}
82
83// ==========================================================================
84// บทที่ 3: ติดตามคำ - เช็ค Google Trends ทุก 1 ชม. ขณะเปิด Claude Code
85// ==========================================================================
86async function checkWatches($: EngineInterface): Promise<void> {
87  if (watches.length === 0) return
88  const trends = await googleTrends($, 'TH')
89  for (const w of watches) {
90    const hit = trends.find(t => t.title.toLowerCase().includes(w.toLowerCase()) || t.news.toLowerCase().includes(w.toLowerCase()))
91    const key = `${w}|${hit?.title}`
92    if (hit && !alerted.has(key)) {
93      alerted.add(key)
94      $.ui.toast(`📈 "${w}" ขึ้นเทรนด์ Google: ${hit.title} (${hit.traffic})`)
95    }
96  }
97}
98
99function startWatch($: EngineInterface): void {
100  watchTimer?.cancel()
101  watchTimer = watches.length ? $.clock.every(60 * 60_000, () => void checkWatches($).catch(() => undefined)) : undefined
102}
103
104const section = (title: string, lines: string[]) => [title, ...lines.map((l, i) => `  ${i + 1}. ${l}`)].join('\n')
105
106const HELP = `📈 Trend TH (Mod 38)
107========================================
108/trend                    สรุปทุกแหล่ง (สั้น)
109/trend google [ประเทศ]    คำค้นมาแรง (ค่าเริ่มต้น TH เช่น US, JP)
110/trend hn                 Hacker News
111/trend github [วัน] [ภาษา]  repo ใหม่ดาวพุ่ง เช่น /trend github 7 typescript
112/trend crypto             เหรียญที่คนค้นมากสุด
113/trend wiki [th|en]       บทความที่คนอ่านมากสุดเมื่อวาน
114/trend watch <คำ> | unwatch <คำ> | watch   เตือนเมื่อคำนี้ขึ้นเทรนด์`
115
116export const register: Register = on => {
117  on('session.start', async ($, e, next) => {
118    const saved = await $.store.get('watches')
119    watches = Array.isArray(saved) ? (saved as string[]) : []
120    startWatch($)
121    await $.command.register({ name: 'trend', description: 'เทรนด์ล่าสุด: Google ไทย, Hacker News, GitHub, คริปโต, Wikipedia (/trend help)', argumentHint: 'google|hn|github|crypto|wiki|watch' })
122    return next(e)
123  })
124
125  // ==========================================================================
126  // บทที่ 4: คำสั่ง /trend
127  // ==========================================================================
128  on('command.run', { command: 'trend' }, async ($, e) => {
129    const [sub = '', a1, a2] = e.args.trim().split(/\s+/).filter(Boolean)
130    try {
131      switch (sub) {
132        case '': {
133          // ดึงพร้อมกัน แหล่งไหนพังก็ยังแสดงแหล่งอื่น
134          const [g, h, gh, c] = await Promise.all([
135            googleTrends($, 'TH').catch(() => [] as GoogleTrend[]),
136            hackerNews($, 5).catch(() => ['(ดึงไม่ได้)']),
137            githubRising($, 7).then(l => l.slice(0, 5)).catch(() => ['(ดึงไม่ได้)']),
138            cryptoTrending($).then(l => l.slice(0, 5)).catch(() => ['(ดึงไม่ได้)']),
139          ])
140          return {
141            text: [
142              section('🇹🇭 Google ไทยวันนี้', g.slice(0, 8).map(t => `${t.title} (${t.traffic})`)),
143              section('💻 Hacker News', h.map(l => l.split('\n')[0] ?? l)),
144              section('⭐ GitHub repo ใหม่ดาวพุ่ง (7 วัน)', gh.map(l => l.split('\n')[0] ?? l)),
145              section('🪙 คริปโตมาแรง', c),
146              '',
147              'ดูละเอียดแต่ละแหล่ง: /trend help',
148            ].join('\n\n'),
149          }
150        }
151        case 'google': {
152          const geo = (a1 ?? 'TH').toUpperCase()
153          const list = await googleTrends($, geo)
154          return { text: section(`🔎 Google Trends ${geo}`, list.slice(0, 20).map(t => `${t.title} (${t.traffic})${t.news ? `\n      📰 ${t.news.slice(0, 90)}` : ''}`)) }
155        }
156        case 'hn':
157          return { text: section('💻 Hacker News ตอนนี้', await hackerNews($, 15)) }
158        case 'github': {
159          const days = Math.min(30, Math.max(1, Number(a1) || 7))
160          const language = a1 && !Number(a1) ? a1 : a2
161          return { text: section(`⭐ GitHub repo ใหม่ (${days} วัน)${language ? ` ${language}` : ''}`, await githubRising($, days, language)) }
162        }
163        case 'crypto':
164          return { text: section('🪙 CoinGecko trending', await cryptoTrending($)) }
165        case 'wiki': {
166          const lang = a1 === 'en' ? 'en' : 'th'
167          return { text: section(`📚 Wikipedia ${lang} อ่านมากสุดเมื่อวาน`, await wikiMostRead($, lang)) }
168        }
169        case 'watch':
170          if (!a1) return { text: watches.length ? `👀 ติดตาม: ${watches.join(', ')}` : 'ยังไม่ได้ติดตามคำไหน /trend watch <คำ>' }
171          watches = [...new Set([...watches, [a1, a2].filter(Boolean).join(' ')])]
172          await $.store.set('watches', watches)
173          startWatch($)
174          await checkWatches($)
175          return { text: `👀 ติดตาม: ${watches.join(', ')} (เช็คทุก 1 ชม. ขณะเปิด Claude Code)` }
176        case 'unwatch':
177          watches = watches.filter(w => w !== [a1, a2].filter(Boolean).join(' '))
178          await $.store.set('watches', watches)
179          startWatch($)
180          return { text: `👀 ติดตาม: ${watches.join(', ') || '-'}` }
181        default:
182          return { text: HELP }
183      }
184    } catch (err) {
185      return { text: `❌ ดึงข้อมูลไม่สำเร็จ: ${err instanceof Error ? err.message : String(err)}` }
186    }
187  })
188}
189
190/**
191 * ========================================================================
192 * การบ้าน / ต่อยอด
193 * ========================================================================
194 * 1. ให้ Claude สรุปว่าเทรนด์ไหนเกี่ยวกับธุรกิจ/โปรเจกต์ของคุณ ($.model.complete)
195 * 2. เก็บประวัติเทรนด์รายวันใน $.store แล้วดูว่าคำไหนอยู่นาน
196 * 3. ส่งสรุปเทรนด์ตอนเช้าไปมือถือ (ต่อกับ Mod 22 + Mod 24)
197 */
198