From 21f49d2ebfa61b7f06a8d6608283421984538f2a Mon Sep 17 00:00:00 2001 From: Koki Takahashi Date: Sun, 29 Sep 2024 14:32:32 +0900 Subject: [PATCH] city-symbol: Add more information to answer message --- city-symbol/index.ts | 94 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 75 insertions(+), 19 deletions(-) diff --git a/city-symbol/index.ts b/city-symbol/index.ts index aa79b53f..d8030d95 100644 --- a/city-symbol/index.ts +++ b/city-symbol/index.ts @@ -3,23 +3,34 @@ import {Mutex} from 'async-mutex'; import {sample} from 'lodash'; import {increment} from '../achievements'; import {AteQuiz, typicalMessageTextsGenerator} from '../atequiz'; +import logger from '../lib/logger'; import {SlackInterface} from '../lib/slack'; import {prefectures} from '../room-gacha/prefectures'; const mutex = new Mutex(); +const log = logger.child({bot: 'city-symbol'}); + interface CitySymbol { prefectureName: string; cityName: string; + cityWikipediaName: string; reason: string; date: string; notes: string; files: string[]; } +interface CityInformation { + placeImage: string; + ruby: string; +} + +type City = CitySymbol & CityInformation; + const getWikipediaSource = async (prefName: string) => { const title = `${prefName}の${prefName === '東京都' ? '区' : ''}市町村章一覧`; - console.log(`Getting wikipedia ${title}...`); + log.info(`Getting wikipedia ${title}...`); const url = `https://ja.wikipedia.org/w/api.php?${qs.encode({ format: 'json', action: 'query', @@ -66,10 +77,12 @@ const getWikipediaSource = async (prefName: string) => { } const [cityName, , reason, date, notes] = columns.slice(-5); + const cityWikipediaName = line.match(new RegExp(`\\[\\[((:?[^|\\]]+\\|)?${cityName})\\]\\]`))?.[1] || ''; citySymbols.push({ prefectureName: prefName, cityName: cityName.trim(), + cityWikipediaName: cityWikipediaName.split('|')[0].trim(), reason: reason.trim(), date: date.trim(), notes: notes.trim().replaceAll(/
/g, '\n'), @@ -80,10 +93,42 @@ const getWikipediaSource = async (prefName: string) => { return citySymbols; }; -const getRandomCitySymbol = async () => { +const getCityInformation = async (title: string): Promise => { + log.info(`Getting wikipedia ${title}...`); + + const url = `https://ja.wikipedia.org/w/api.php?${qs.encode({ + format: 'json', + action: 'query', + prop: 'revisions', + rvprop: 'content', + titles: title, + })}`; + + const response = await fetch(url); + const json = await response.json(); + + const pages = json?.query?.pages; + const content = pages?.[Object.keys(pages)[0]]?.revisions?.[0]?.['*']; + if (!content) { + throw new Error('Failed to get wikipedia source'); + } + + const placeImageMatches = content.match(/位置画像.+\|image\s*=\s*(.+?)\|/); + const placeImage = placeImageMatches?.[1] ?? ''; + + const rubyMatches = content.match(/((.+?))は/); + const ruby = rubyMatches?.[1] ?? ''; + + return {placeImage, ruby}; +}; + +const getRandomCitySymbol = async (): Promise => { const prefectureChosen = sample(Object.keys(prefectures)); const citySymbols = await getWikipediaSource(prefectureChosen); - return sample(citySymbols); + const citySymbol = sample(citySymbols); + const cityInformation = await getCityInformation(citySymbol.cityWikipediaName); + + return {...citySymbol, ...cityInformation}; }; const getWikimediaImageUrl = (fileName: string) => `https://commons.wikimedia.org/wiki/Special:FilePath/${qs.escape(fileName)}?width=200`; @@ -108,9 +153,18 @@ export default async (slackClients: SlackInterface) => { message.text && message.text.match(/^(?:市?区?町?村?)章当てクイズ$/) ) { - const citySymbol = await getRandomCitySymbol(); + const city = await getRandomCitySymbol(); const quizText = 'この市区町村章ど~こだ?'; - const imageUrl = getWikimediaImageUrl(sample(citySymbol.files)); + const imageUrl = getWikimediaImageUrl(sample(city.files)); + const correctAnswers = [ + `${city.prefectureName}${city.cityName}`, + city.cityName, + city.cityName.replace(/(市|区|町|村)$/, ''), + ...(city.ruby ? [ + city.ruby, + city.ruby.replace(/(し|く|ちょう|まち|そん|むら)$/, ''), + ] : []), + ]; const problem = { problemMessage: { channel: message.channel, @@ -133,7 +187,7 @@ export default async (slackClients: SlackInterface) => { hintMessages: [ { channel: message.channel, - text: `ヒント: ${citySymbol.prefectureName}の市区町村ですよ~`, + text: `ヒント: ${city.prefectureName}の市区町村ですよ~`, }, ], immediateMessage: { @@ -156,12 +210,12 @@ export default async (slackClients: SlackInterface) => { }, solvedMessage: { channel: message.channel, - text: typicalMessageTextsGenerator.solved(` *${citySymbol.prefectureName}${citySymbol.cityName}* `), + text: typicalMessageTextsGenerator.solved(` *${city.prefectureName}${city.cityName}* `), reply_broadcast: true, }, unsolvedMessage: { channel: message.channel, - text: typicalMessageTextsGenerator.unsolved(` *${citySymbol.prefectureName}${citySymbol.cityName}* `), + text: typicalMessageTextsGenerator.unsolved(` *${city.prefectureName}${city.cityName}* `), reply_broadcast: true, }, answerMessage: { @@ -171,27 +225,29 @@ export default async (slackClients: SlackInterface) => { { type: 'image', image_url: imageUrl, - alt_text: citySymbol.cityName, + alt_text: city.cityName, }, { type: 'section', text: { type: 'mrkdwn', text: [ - `*${citySymbol.prefectureName}${citySymbol.cityName}*`, - `${citySymbol.reason}`, - `制定年月日: ${citySymbol.date}`, - `備考: ${citySymbol.notes || 'なし'}`, + `*${city.prefectureName}${city.cityName}*`, + `${city.reason}`, + `制定年月日: ${city.date}`, + `備考: ${city.notes || 'なし'}`, + `有効回答一覧: ${correctAnswers.join(', ')}`, ].join('\n'), }, }, + { + type: 'image', + image_url: getWikimediaImageUrl(city.placeImage), + alt_text: city.cityName, + }, ], }, - correctAnswers: [ - citySymbol.cityName, - citySymbol.cityName.replace(/(市|区|町|村)$/g, ''), - `${citySymbol.prefectureName}${citySymbol.cityName}`, - ], + correctAnswers, }; const quiz = new CitySymbolAteQuiz(slackClients, problem, { @@ -205,7 +261,7 @@ export default async (slackClients: SlackInterface) => { } } } catch (error) { - console.error(error.stack); + log.error(error.stack); await slackClients.webClient.chat.postMessage({ channel: message.channel,