|
| 1 | +// JANOGのAPIデータを元に、セッション・スピーカー情報を生成するスクリプト |
| 2 | +// APIエンドポイント: |
| 3 | +// - https://ai-chat.janog57.sakura.ad.jp/api/rooms/ |
| 4 | +// - https://ai-chat.janog57.sakura.ad.jp/api/programs/ |
| 5 | +// - https://ai-chat.janog57.sakura.ad.jp/api/speakers/ |
| 6 | +// 変換先フォーマット: src/data/xxx.ts (Track, Speaker, Talk) |
| 7 | +// 実行コマンド: npx tsx ./script/janog/convert.ts && npm run fmt |
| 8 | + |
| 9 | +import { janogProgramItem, janogSpeaker, janogRoom } from './type.js' |
| 10 | +import { Track, Speaker, Talk } from '../../src/data/types.js' |
| 11 | +import { exportEventData } from '../common/utils.js' |
| 12 | + |
| 13 | +const API_BASE_URL: string = 'https://ai-chat.janog57.sakura.ad.jp/api' |
| 14 | + |
| 15 | +const eventImageUrl: string = |
| 16 | + 'https://www.janog.gr.jp/meeting/janog57/wp-content/uploads/2024/11/JANOG57-logo.png' |
| 17 | + |
| 18 | +const conferenceDays = [ |
| 19 | + { id: 1, date: '2026-02-11' }, |
| 20 | + { id: 2, date: '2026-02-12' }, |
| 21 | + { id: 3, date: '2026-02-13' }, |
| 22 | +] |
| 23 | + |
| 24 | +// 手動で追加するトーク(最小限の情報) |
| 25 | +// IDは 9000番台を使用して自動生成されるIDと重複を避ける |
| 26 | +const manualTalks: Partial<Talk>[] = [ |
| 27 | + // 必要に応じて追加 |
| 28 | +] |
| 29 | + |
| 30 | +/** |
| 31 | + * メイン処理関数 |
| 32 | + */ |
| 33 | +async function main() { |
| 34 | + // APIからデータを取得する |
| 35 | + const [rooms, programs, speakersData] = await Promise.all([ |
| 36 | + fetchRooms(), |
| 37 | + fetchPrograms(), |
| 38 | + fetchSpeakers(), |
| 39 | + ]) |
| 40 | + |
| 41 | + // Track情報を生成する |
| 42 | + const tracks: Track[] = convertToTracks(rooms) |
| 43 | + |
| 44 | + // Speaker情報を生成する(id:0でJANOG57を追加) |
| 45 | + const speakers: Speaker[] = [ |
| 46 | + { |
| 47 | + id: 0, |
| 48 | + name: 'JANOG57', |
| 49 | + avatarUrl: eventImageUrl, |
| 50 | + }, |
| 51 | + ...convertToSpeakers(speakersData), |
| 52 | + ] |
| 53 | + |
| 54 | + // Talk情報を生成する |
| 55 | + const talks: Talk[] = convertToTalks(programs, speakers, rooms) |
| 56 | + |
| 57 | + // 手動で追加したトークをマージ |
| 58 | + const allTalks: Talk[] = [...(manualTalks as Talk[]), ...talks].sort( |
| 59 | + (a, b) => { |
| 60 | + if (a.startTime < b.startTime) return -1 |
| 61 | + if (a.startTime > b.startTime) return 1 |
| 62 | + return 0 |
| 63 | + } |
| 64 | + ) |
| 65 | + |
| 66 | + // 最終データを組み立てる |
| 67 | + exportEventData({ tracks, speakers, talks: allTalks }) |
| 68 | +} |
| 69 | + |
| 70 | +/** |
| 71 | + * Rooms APIからデータを取得する |
| 72 | + */ |
| 73 | +async function fetchRooms(): Promise<janogRoom[]> { |
| 74 | + const url = `${API_BASE_URL}/rooms/` |
| 75 | + const data: janogRoom[] = await fetch(url).then((res) => res.json()) |
| 76 | + return data |
| 77 | +} |
| 78 | + |
| 79 | +/** |
| 80 | + * Programs APIからデータを取得する |
| 81 | + */ |
| 82 | +async function fetchPrograms(): Promise<janogProgramItem[]> { |
| 83 | + const url = `${API_BASE_URL}/programs/` |
| 84 | + const data: janogProgramItem[] = await fetch(url).then((res) => res.json()) |
| 85 | + return data |
| 86 | +} |
| 87 | + |
| 88 | +/** |
| 89 | + * Speakers APIからデータを取得する |
| 90 | + */ |
| 91 | +async function fetchSpeakers(): Promise<janogSpeaker[]> { |
| 92 | + const url = `${API_BASE_URL}/speakers/` |
| 93 | + const data: janogSpeaker[] = await fetch(url).then((res) => res.json()) |
| 94 | + return data |
| 95 | +} |
| 96 | + |
| 97 | +/** |
| 98 | + * RoomデータからTrack情報を生成する |
| 99 | + */ |
| 100 | +function convertToTracks(rooms: janogRoom[]): Track[] { |
| 101 | + return rooms.map((room) => ({ |
| 102 | + id: room.id, |
| 103 | + name: room.name, |
| 104 | + hashTag: 'janog57', |
| 105 | + })) |
| 106 | +} |
| 107 | + |
| 108 | +/** |
| 109 | + * SpeakerデータからSpeaker情報を生成する |
| 110 | + */ |
| 111 | +function convertToSpeakers(speakersData: janogSpeaker[]): Speaker[] { |
| 112 | + const DEFAULT_IMAGE_PATH: string = eventImageUrl |
| 113 | + |
| 114 | + return speakersData.map((speaker) => ({ |
| 115 | + id: speaker.id, |
| 116 | + name: speaker.name, |
| 117 | + avatarUrl: speaker.avatar_url || DEFAULT_IMAGE_PATH, |
| 118 | + })) |
| 119 | +} |
| 120 | + |
| 121 | +/** |
| 122 | + * プログラムデータからTalk情報を生成する |
| 123 | + */ |
| 124 | +function convertToTalks( |
| 125 | + programs: janogProgramItem[], |
| 126 | + speakers: Speaker[], |
| 127 | + rooms: janogRoom[] |
| 128 | +): Talk[] { |
| 129 | + const convertedTalks: Talk[] = [] |
| 130 | + |
| 131 | + programs |
| 132 | + .sort((a, b) => { |
| 133 | + // 日付と開始時間でソート |
| 134 | + const aDateTime = `${a.date}T${a.start_time}` |
| 135 | + const bDateTime = `${b.date}T${b.start_time}` |
| 136 | + if (aDateTime < bDateTime) return -1 |
| 137 | + if (aDateTime > bDateTime) return 1 |
| 138 | + return 0 |
| 139 | + }) |
| 140 | + .forEach((program) => { |
| 141 | + const room = rooms.find((r) => r.id === program.room.id) |
| 142 | + if (!room) { |
| 143 | + console.warn( |
| 144 | + `No room found for program: ${program.title} (room id: ${program.room.id})` |
| 145 | + ) |
| 146 | + return |
| 147 | + } |
| 148 | + |
| 149 | + // スピーカー情報を取得(いない場合は「JANOG57」を割り当て) |
| 150 | + const talkSpeakers = |
| 151 | + program.speakers && program.speakers.length > 0 |
| 152 | + ? program.speakers.map((s: janogSpeaker) => { |
| 153 | + const speaker = speakers.find((sp) => sp.id === s.id) |
| 154 | + return { |
| 155 | + id: speaker?.id || s.id, |
| 156 | + name: speaker?.name || s.name, |
| 157 | + } |
| 158 | + }) |
| 159 | + : [{ id: 0, name: 'JANOG57' }] |
| 160 | + |
| 161 | + // startTimeとendTimeを生成(ISO 8601形式) |
| 162 | + // APIが HH:MM:SS 形式を返す場合はそのまま、HH:MM 形式なら :00 を追加 |
| 163 | + const formatTime = (time: string) => |
| 164 | + time.split(':').length === 2 ? `${time}:00` : time |
| 165 | + const startTime = `${program.date}T${formatTime(program.start_time)}+09:00` |
| 166 | + const endTime = `${program.date}T${formatTime(program.end_time)}+09:00` |
| 167 | + |
| 168 | + convertedTalks.push({ |
| 169 | + id: program.id, |
| 170 | + trackId: room.id, |
| 171 | + title: program.title, |
| 172 | + abstract: program.abstract?.replace(/[\r\t\n]/g, '') || '', |
| 173 | + speakers: talkSpeakers, |
| 174 | + startTime, |
| 175 | + endTime, |
| 176 | + conferenceDayId: conferenceDays.find((day) => program.date === day.date) |
| 177 | + ?.id, |
| 178 | + } as Talk) |
| 179 | + }) |
| 180 | + |
| 181 | + return convertedTalks |
| 182 | +} |
| 183 | + |
| 184 | +main().catch(console.error) |
0 commit comments