このコミットが含まれているのは:
2026-06-08 08:41:52 +09:00
コミット de21141f5a
5個のファイルの変更468行の追加240行の削除
+48 -60
ファイルの表示
@@ -17,11 +17,9 @@ export type GekanatorAnswerLog = {
export type GekanatorQuestionKind =
| 'tag'
| 'title'
| 'date'
| 'media'
| 'source'
| 'structure'
| 'title'
| 'original_date'
export type GekanatorQuestion = {
id: string
@@ -54,6 +52,19 @@ const hostOf = (post: Post): string | null => {
}
const originalYearOf = (post: Post): number | null => {
const value = post.originalCreatedFrom || post.originalCreatedBefore
if (!(value))
return null
const date = new Date (value)
if (Number.isNaN (date.getTime ()))
return null
return date.getFullYear ()
}
const tagQuestionKey = ({ category, name }: { category: string; name: string }): string =>
`${ category }:${ name }`
@@ -113,9 +124,11 @@ export const buildGekanatorQuestions = (posts: Post[]): GekanatorQuestion[] => {
&& !(tag.name.includes ('bot操作')))
.map (tag => tagQuestionKey (tag))))
const hosts = countBy (posts.map (hostOf).filter ((host): host is string => Boolean (host)))
const tagMedian = median (posts.map (post => post.tags.length))
const originalYears = countBy (
posts
.map (originalYearOf)
.filter ((year): year is number => year !== null))
const titleLengthMedian = median (posts.map (post => post.title?.length ?? 0))
const currentYear = new Date ().getFullYear ()
const usefulEntries = <T extends string | number> (counts: Map<T, number>) =>
[...counts.entries ()]
@@ -144,63 +157,41 @@ export const buildGekanatorQuestions = (posts: Post[]): GekanatorQuestion[] => {
.filter (([, count]) => count >= 2 && count <= Math.max (2, posts.length * .7))
.slice (0, 20)
.map (([host]) => ({
id: `source:${ host }`,
text: `${ host } の投稿を思ひ浮かべてゐる?`,
kind: 'source' as const,
test: (post: Post) => hostOf (post) === host }))
id: `source:${ host }`,
text: `${ host } の投稿を思ひ浮かべてゐる?`,
kind: 'source' as const,
test: (post: Post) => hostOf (post) === host }))
return [
...sourceQuestions,
{
id: 'title:present',
text: '題名が付いてゐる投稿?',
kind: 'title',
test: post => Boolean (post.title) },
const originalYearQuestions = usefulEntries (originalYears)
.filter (([, count]) => count >= 2 && count <= Math.max (2, posts.length * .7))
.slice (0, 20)
.map (([year]) => ({
id: `original-year:${ year }`,
text: `オリジナルの投稿年は ${ year } 年?`,
kind: 'original_date' as const,
test: (post: Post) => originalYearOf (post) === year }))
const titleQuestions = [
{
id: 'title:long',
text: '題名が長めの投稿?',
kind: 'title',
test: post => (post.title?.length ?? 0) > titleLengthMedian },
kind: 'title' as const,
test: (post: Post) => (post.title?.length ?? 0) > titleLengthMedian },
{
id: 'title:ascii',
text: '題名に英数字が混じってゐる?',
kind: 'title',
test: post => /[A-Za-z0-9]/.test (post.title ?? '') },
{
id: 'media:thumbnail',
text: 'ぱっと見でサムネが付いてゐる投稿?',
kind: 'media',
test: post => Boolean (post.thumbnail || post.thumbnailBase) },
{
id: 'media:video-source',
text: '動画として見られる投稿?',
kind: 'media',
test: post => /nicovideo|youtube|youtu\.be/.test (post.url) },
{
id: 'structure:many-tags',
text: 'タグが多めに付いてゐる投稿?',
kind: 'structure',
test: post => post.tags.length > tagMedian },
{
id: 'structure:no-title',
text: '題名がまだ付いてゐない投稿?',
kind: 'structure',
test: post => !(post.title) },
{
id: 'date:recent',
text: '最近追加されたほうの投稿?',
kind: 'date',
test: post => new Date (post.createdAt).getFullYear () >= currentYear - 1 },
{
id: 'date:old',
text: 'むかし追加されたほうの投稿?',
kind: 'date',
test: post => new Date (post.createdAt).getFullYear () <= currentYear - 3 },
{
id: 'date:original-known',
text: 'オリジナルの投稿日時が分かってゐる投稿?',
kind: 'date',
test: post => Boolean (post.originalCreatedFrom || post.originalCreatedBefore) },
kind: 'title' as const,
test: (post: Post) => /[A-Za-z0-9]/.test (post.title ?? '') }]
.filter (question => {
const yes = posts.filter (post => question.test (post)).length
const no = posts.length - yes
return yes >= 2 && no >= 2 && yes <= posts.length * .7 && no <= posts.length * .7
})
return [
...sourceQuestions,
...originalYearQuestions,
...titleQuestions,
...tagQuestions]
}
@@ -208,18 +199,15 @@ export const buildGekanatorQuestions = (posts: Post[]): GekanatorQuestion[] => {
export const saveGekanatorGame = async ({
guessedPostId,
correctPostId,
won,
answers,
}: {
guessedPostId: number
correctPostId: number | null
won: boolean
correctPostId: number
answers: GekanatorAnswerLog[]
}): Promise<{ id: number }> =>
await apiPost ('/gekanator/games', {
guessed_post_id: guessedPostId,
correct_post_id: correctPostId,
won,
question_count: answers.length,
answers: answers.map (answer => ({
question_id: answer.questionId,