บุคคลสำคัญประจำวันจาก Wikipedia: ใครเกิดวันนี้ในประวัติศาสตร์ เรื่องย่อ และบทเรียนที่ Claude สรุปให้

บุคคลสำคัญประจำวันจากข้อมูลจริงของ Wikipedia "On this day": คัดคนที่เกิดวันนี้ในอาชีพที่สร้างแรงบันดาลใจ (นักวิทยาศาสตร์ นักประดิษฐ์ นักเขียน ผู้นำ ศิลปิน ...) แล้วให้ Claude สรุปเป็นภาษาไทยว่าเขาเป็นใคร ทำอะไรสำคัญ และข้อคิดสำหรับ Dev ค้นประวัติคนอื่นได้ด้วย
| คำสั่ง | ทำอะไร |
|---|---|
/figure | บุคคลประจำวันนี้ |
/figure list | ทุกคนที่เกิดวันนี้ (คัดแล้ว) |
/figure random | สุ่มอีกคน |
/figure date <วัน/เดือน> | ดูวันอื่น |
/figure search <ชื่อ> | ประวัติคนที่สนใจ (ชื่อภาษาอังกฤษ) |
/figure fav [ชื่อ] | คนที่ชอบ |
/plugin install daily-figures-th --marketplace tksoft11/claude-code-mods-th
ต้องตั้ง CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1 ก่อนเปิด claude (ดู README หลัก)
เปิด hooks/register.tsx แล้วอ่านตามบทที่เขียนไว้ในคอมเมนต์:
API On this day รองรับเฉพาะ Wikipedia ภาษาอังกฤษ จึงดึงจากฉบับอังกฤษแล้วสรุปเป็นไทย ส่วน "ข้อคิด" เป็นการตีความของ Claude
hooks/register.tsx 175 lines1/**
2 * ============================================================================
3 * MOD 34: Daily Figures TH - บุคคลสำคัญประจำวันจาก Wikipedia (ฉบับสอนทำ)
4 * ============================================================================
5 * แรงบันดาลใจจาก: หน้า "On this day" ของ Wikipedia
6 *
7 * หน้าที่ของ Mod นี้:
8 * 1. วันนี้ในประวัติศาสตร์มีใครเกิดบ้าง (ข้อมูลจริงจาก Wikipedia "On this day")
9 * 2. เลือกบุคคลเด่นประจำวัน (นักวิทยาศาสตร์ นักประดิษฐ์ นักเขียน ผู้นำ ศิลปิน ...)
10 * 3. ให้ Claude สรุปเป็นภาษาไทย: เขาเป็นใคร ทำอะไรสำคัญ และบทเรียนสำหรับ Dev
11 * 4. ค้นประวัติบุคคลอื่น, เหตุการณ์สำคัญวันนี้, บันทึกคนที่ชอบ
12 *
13 * หมายเหตุ: API "On this day" รองรับเฉพาะ Wikipedia ภาษาอังกฤษ จึงดึงจากฉบับอังกฤษแล้วให้ Claude สรุปเป็นไทย
14 */
15
16import type { EngineInterface, Register } from 'claude-code'
17
18interface Person {
19 name: string
20 year: number
21 text: string // ข้อความสั้นจาก Wikipedia
22 description: string
23 extract: string
24 url: string
25}
26
27const UA = { 'User-Agent': 'claude-code-daily-figures-th/1.0 (educational mod)' }
28// อาชีพที่ถือว่า "สร้างแรงบันดาลใจ" ใช้คัดเลือกคนประจำวัน
29// \b = ต้องตรงทั้งคำ (ไม่ให้ "songwriter" ตรงกับ writer หรือ "artistic" ตรงกับ artist)
30const INSPIRING = /\b(scientist|physicist|chemist|mathematician|inventor|engineer|computer scientist|programmer|astronomer|biologist|physician|philosopher|writer|novelist|poet|composer|painter|artist|architect|explorer|entrepreneur|businessman|economist|president|prime minister|king|queen|emperor|activist|educator|nobel)\b/i
31
32let favorites: { name: string; year: number }[] = []
33let cache: { key: string; people: Person[] } | undefined
34
35// ==========================================================================
36// บทที่ 1: ดึงรายชื่อคนที่เกิดวันนี้ (เดือน/วัน) จาก Wikimedia API
37// ==========================================================================
38async function bornOn($: EngineInterface, month: number, day: number): Promise<Person[]> {
39 const key = `${month}/${day}`
40 if (cache?.key === key) return cache.people
41 const mm = String(month).padStart(2, '0')
42 const dd = String(day).padStart(2, '0')
43 const res = await $.http.fetch(`https://api.wikimedia.org/feed/v1/wikipedia/en/onthisday/births/${mm}/${dd}`, { headers: UA })
44 if (!res.ok) throw new Error(`HTTP ${res.status}`)
45 const body = JSON.parse(res.text) as { births?: { year: number; text: string; pages?: { description?: string; extract?: string; content_urls?: { desktop?: { page?: string } } }[] }[] }
46 const people = (body.births ?? []).map(b => {
47 const p = b.pages?.[0]
48 return { name: b.text.split(',')[0] ?? b.text, year: b.year, text: b.text, description: p?.description ?? '', extract: p?.extract ?? '', url: p?.content_urls?.desktop?.page ?? '' }
49 })
50 cache = { key, people }
51 return people
52}
53
54// ==========================================================================
55// บทที่ 2: เลือกคนประจำวัน - คัดอาชีพที่น่าสนใจ แล้วเลือกตามวันที่ (ได้คนเดิมทั้งวัน)
56// ==========================================================================
57// อาชีพที่คำตรงกันแต่ไม่ใช่ความหมายที่ต้องการ เช่น "martial artist", "musical artist"
58const EXCLUDE = /martial artist|musical artist|storyboard artist|voice actor|actress|singer|gymnast|footballer|wrestler/i
59
60function candidates(people: Person[]): Person[] {
61 return people.filter(p => INSPIRING.test(`${p.description} ${p.text}`) && !EXCLUDE.test(p.description) && p.extract.length > 200)
62}
63
64function pickOfDay(people: Person[], now: number): Person | undefined {
65 const list = candidates(people)
66 const d = new Date(now)
67 return list[(d.getFullYear() * 31 + d.getDate()) % Math.max(1, list.length)]
68}
69
70// ==========================================================================
71// บทที่ 3: ให้ Claude สรุปเป็นไทย + บทเรียน (ใช้เฉพาะข้อมูลจาก Wikipedia)
72// ==========================================================================
73async function thaiStory($: EngineInterface, p: Person): Promise<string> {
74 const system = 'You write short, accurate summaries of real people for Thai software developers. Write the whole answer in Thai (ภาษาไทย); keep names in their original spelling. Use only the facts in the text given; do not add facts. Format: 1) who they are (1-2 sentences) 2) what they did that mattered (2-3 bullets) 3) one lesson a developer can take from their life (1-2 sentences, marked as ข้อคิด).'
75 const prompt = `สรุปเป็นภาษาไทย:\n\n${p.name}${p.year ? ` (born ${p.year})` : ''}\n${p.description}\n\n${p.extract}`
76 for (const model of ['haiku', await $.session.model()]) {
77 const r = await $.model.complete({ model, system, prompt, maxTokens: 900 }).catch(() => undefined)
78 if (r?.isAnswered) return r.text.trim()
79 }
80 return p.extract // ใช้ model ไม่ได้ ก็แสดงต้นฉบับภาษาอังกฤษ
81}
82
83async function personCard($: EngineInterface, p: Person, now: number): Promise<string> {
84 const born = p.year ? ` (เกิด ค.ศ. ${p.year}, ${new Date(now).getFullYear() - p.year} ปีที่แล้ว)` : ''
85 return [`🌟 ${p.name}${born}`, p.description, '', await thaiStory($, p), '', `อ่านต่อ: ${p.url}`].join('\n')
86}
87
88async function searchPerson($: EngineInterface, name: string): Promise<Person | undefined> {
89 const res = await $.http.fetch(`https://en.wikipedia.org/api/rest_v1/page/summary/${encodeURIComponent(name.replace(/ /g, '_'))}`, { headers: UA }).catch(() => undefined)
90 if (!res?.ok) return undefined
91 const b = JSON.parse(res.text) as { title?: string; description?: string; extract?: string; type?: string; content_urls?: { desktop?: { page?: string } } }
92 if (!b.extract || b.type === 'disambiguation') return undefined
93 // ปีเกิดมักอยู่ใน description เช่น "English mathematician (1815–1852)"; หาไม่เจอ = 0 (ไม่แสดง)
94 const year = Number(/\((?:born )?(\d{3,4})/.exec(b.description ?? '')?.[1] ?? /\(born[^)]*?(\d{4})\)/.exec(b.extract)?.[1] ?? 0)
95 return { name: b.title ?? name, year, text: b.title ?? name, description: b.description ?? '', extract: b.extract, url: b.content_urls?.desktop?.page ?? '' }
96}
97
98const HELP = `🌟 Daily Figures TH (Mod 34)
99========================================
100/figure บุคคลสำคัญประจำวันนี้ (สรุปไทยโดย Claude)
101/figure list ทุกคนที่เกิดวันนี้ (คัดเฉพาะอาชีพน่าสนใจ)
102/figure random สุ่มอีกคนที่เกิดวันนี้
103/figure date <วัน/เดือน> ดูวันอื่น เช่น /figure date 9/1
104/figure search <ชื่อ> ประวัติคนที่สนใจ (ชื่อภาษาอังกฤษ เช่น Ada Lovelace)
105/figure fav [ชื่อ] บันทึก/ดูคนที่ชอบ`
106
107export const register: Register = on => {
108 on('session.start', async ($, e, next) => {
109 const saved = await $.store.get('favorites')
110 favorites = Array.isArray(saved) ? (saved as typeof favorites) : []
111 await $.command.register({ name: 'figure', description: 'บุคคลสำคัญประจำวันจาก Wikipedia สรุปไทย (/figure help)', argumentHint: 'list|random|date|search|fav' })
112 return next(e)
113 })
114
115 // ==========================================================================
116 // บทที่ 4: คำสั่ง /figure
117 // ==========================================================================
118 on('command.run', { command: 'figure' }, async ($, e) => {
119 const [sub = '', ...rest] = e.args.trim().split(/\s+/).filter(Boolean)
120 const now = await $.clock.now()
121 const d = new Date(now)
122 try {
123 switch (sub) {
124 case '':
125 case 'today': {
126 const p = pickOfDay(await bornOn($, d.getMonth() + 1, d.getDate()), now)
127 return { text: p ? await personCard($, p, now) : 'ไม่พบข้อมูลวันนี้' }
128 }
129 case 'list': {
130 const list = candidates(await bornOn($, d.getMonth() + 1, d.getDate()))
131 return { text: [`📜 คนสำคัญที่เกิดวันที่ ${d.getDate()}/${d.getMonth() + 1} (${list.length} คน)`, ...list.slice(0, 40).map(p => ` ${p.year} ${p.name} - ${p.description}`)].join('\n') }
132 }
133 case 'random': {
134 const list = candidates(await bornOn($, d.getMonth() + 1, d.getDate()))
135 const p = list[Math.floor(Math.random() * list.length)]
136 return { text: p ? await personCard($, p, now) : 'ไม่พบข้อมูล' }
137 }
138 case 'date': {
139 const m = /^(\d{1,2})\/(\d{1,2})$/.exec(rest[0] ?? '')
140 if (!m) return { text: 'วิธีใช้: /figure date 9/1 (วัน/เดือน)' }
141 const people = await bornOn($, Number(m[2]), Number(m[1]))
142 const p = pickOfDay(people, new Date(d.getFullYear(), Number(m[2]) - 1, Number(m[1])).getTime())
143 return { text: p ? await personCard($, p, now) : 'ไม่พบข้อมูลวันนั้น' }
144 }
145 case 'search': {
146 const name = rest.join(' ')
147 if (!name) return { text: 'วิธีใช้: /figure search Ada Lovelace' }
148 const p = await searchPerson($, name)
149 return { text: p ? await personCard($, p, now) : `ไม่พบ "${name}" ลองพิมพ์ชื่อเต็มภาษาอังกฤษ` }
150 }
151 case 'fav': {
152 const name = rest.join(' ')
153 if (!name) return { text: favorites.length ? ['⭐ คนที่ชอบ', ...favorites.map(f => ` ${f.name} (${f.year})`)].join('\n') : 'ยังไม่มี /figure fav <ชื่อ>' }
154 favorites = favorites.some(f => f.name === name) ? favorites.filter(f => f.name !== name) : [...favorites, { name, year: 0 }]
155 await $.store.set('favorites', favorites)
156 return { text: favorites.some(f => f.name === name) ? `⭐ บันทึก ${name} แล้ว` : `☆ เอา ${name} ออกแล้ว` }
157 }
158 default:
159 return { text: HELP }
160 }
161 } catch (err) {
162 return { text: `❌ ดึงข้อมูลไม่สำเร็จ: ${err instanceof Error ? err.message : String(err)}` }
163 }
164 })
165}
166
167/**
168 * ========================================================================
169 * การบ้าน / ต่อยอด
170 * ========================================================================
171 * 1. ดึงเหตุการณ์สำคัญวันนี้ด้วย endpoint onthisday/events (โครงสร้างเดียวกัน)
172 * 2. แสดงบุคคลประจำวันอัตโนมัติตอนเปิด Claude Code ครั้งแรกของวัน (ดูวิธีใน Mod 32)
173 * 3. เน้นบุคคลในวงการคอมพิวเตอร์: กรองด้วย description ที่มีคำว่า computer/programmer
174 */
175