Skip to content

Commit

Permalink
add post: 2024 年 5 月 振り返り (#322)
Browse files Browse the repository at this point in the history
* add

* implement card link

* style: $ bun run format

* update bun.lockb

* update date

* refine fetching script

* collapse descriptions

* add mergin to cardlink

* linkize

* adjust style

* remove invalid og image

* adjust for mobile

* いつまでセールやってるかわからないので消す

---------

Co-authored-by: fohte-bot[bot] <139195068+fohte-bot[bot]@users.noreply.github.com>
  • Loading branch information
fohte and fohte-bot[bot] authored Jun 6, 2024
1 parent b7524e2 commit ed68174
Show file tree
Hide file tree
Showing 7 changed files with 332 additions and 0 deletions.
Binary file modified bun.lockb
Binary file not shown.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,14 @@
},
"devDependencies": {
"@fohte/eslint-config": "0.0.4",
"@types/jsdom": "21.1.7",
"@types/react": "18.2.73",
"@types/react-dom": "18.2.23",
"bun-types": "1.0.36",
"concurrently": "8.2.2",
"eslint": "8.56.0",
"eslint-config-next": "14.1.4",
"jsdom": "24.1.0",
"prettier": "3.2.5",
"textlint": "14.0.4",
"textlint-rule-preset-ja-technical-writing": "9.0.0",
Expand Down
88 changes: 88 additions & 0 deletions scripts/fetch-ogp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { promises as fs } from 'fs'
import { JSDOM } from 'jsdom'

const files = await fs.readdir('./src/contents/posts')

const urls = (
await Promise.all(
files.map(async (file) => {
// extract urls from CardLink components
const content = await fs.readFile(`./src/contents/posts/${file}`, 'utf-8')

const regex = /<CardLink href="(.+)"/g
const matches = content.matchAll(regex)

return Array.from(matches, (match) => match[1])
}),
)
).flat()

console.log(urls)

type Data = {
[url: string]: {
title?: string | null
description?: string | null
image?: string | null
}
}

const fetchOgp = async (url: string): Promise<Data[string]> => {
const res = await fetch(url)
const html = await res.text()
const dom = new JSDOM(html)

const data = {
title: dom.window.document
.querySelector('meta[property="og:title"]')
?.getAttribute('content'),
description: dom.window.document
.querySelector('meta[property="og:description"]')
?.getAttribute('content'),
image: dom.window.document
.querySelector('meta[property="og:image"]')
?.getAttribute('content'),
}

if (data.title == null) {
data.title = dom.window.document.querySelector('title')?.textContent
}

if (data.image == null) {
// if amazon
if (res.url.startsWith('https://www.amazon.co.jp/')) {
const asin = res.url.match(/dp\/(\w+)/)?.[1]
if (asin == null) {
throw new Error(`ASIN not found: ${res.url}`)
}

data.image = `https://images.amazon.com/images/P/${asin}.09_SL110_.jpg`
} else {
// favicon
data.image = dom.window.document
.querySelector('link[rel="icon"]')
?.getAttribute('href')
}
}

return data
}

const json: Data = JSON.parse(await fs.readFile('./src/data/ogp.json', 'utf-8'))

for (const url of urls) {
if (json[url] != null) {
continue
}

console.log(`fetching ${url}...`)
const data = await fetchOgp(url)

json[url] = data
fs.writeFile('./src/data/ogp.json', JSON.stringify(json, null, 2) + '\n')

console.log(`fetched ${url}`)

// sleep
await new Promise((resolve) => setTimeout(resolve, 1000))
}
81 changes: 81 additions & 0 deletions src/components/mdx/CardLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
'use client'

import { Box, Image, Text } from '@chakra-ui/react'

import { type LinkProps } from '@/components/Link'
import ogpData from '@/data/ogp.json'

export interface Props extends LinkProps {
href: string
}

type OgpData = {
[url: string]: {
title?: string | null
description?: string | null
image?: string | null
}
}

const collapseDescription = (description: string): string => {
const maxLength = 80

const newDescription = description.substring(0, maxLength)
if (description !== newDescription) {
return `${newDescription}…`
}
return newDescription
}

export const CardLink: React.FC<Props> = ({ href }) => {
const ogp = (ogpData as OgpData)[href]

if (ogp == null) {
throw new Error(`OGP not found: ${href}`)
}

const domain = new URL(href).hostname

return (
<Box
as="a"
href={href}
borderWidth="1px"
borderColor="gray.200"
rounded="md"
overflow="hidden"
textDecoration="none"
display="flex"
alignItems="center"
justifyContent="center"
my={4}
py={4}
px={4}
gap={6}
>
{ogp.image && (
<Image
src={ogp.image}
alt="Link preview image"
height="100%"
maxW="min(40%, 250px)"
maxH="200px"
objectFit="cover"
/>
)}
<Box flex="1">
<Text fontSize="md" fontWeight="bold">
{ogp.title}
</Text>
<Text fontSize="sm" color="gray.600">
{domain}
</Text>
{ogp.description && (
<Text fontSize="xs" color="gray.600" mt={1}>
{collapseDescription(ogp.description)}
</Text>
)}
</Box>
</Box>
)
}
6 changes: 6 additions & 0 deletions src/components/mdx/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { MDXComponents } from 'mdx/types'
import * as React from 'react'

import { Link } from '@/components/Link'
import { CardLink } from '@/components/mdx/CardLink'
import { CodeBlock } from '@/components/mdx/CodeBlock'
import { DocsHeading } from '@/components/mdx/DocsHeading'
import { Image } from '@/components/mdx/Image'
Expand Down Expand Up @@ -69,6 +70,7 @@ export const mdxComponents: MDXComponents = {
},
hr: (props) => <Divider mt={8} mb={8} {...props} />,
a: Link,
CardLink: CardLink,
p: (props) => <Text as="p" mt={4} {...props} />,

// FIXME: fix any type
Expand Down Expand Up @@ -120,4 +122,8 @@ export const rssComponents: MDXComponents = {
Mastodon: <a>{url}</a>
</p>
),

CardLink: ({ href, children }: React.ComponentProps<typeof CardLink>) => (
<a href={href}>{children}</a>
),
}
121 changes: 121 additions & 0 deletions src/contents/posts/202405-review.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
---
title: '2024 年 5 月 振り返り'
date: '2024-06-07T00:45:54+09:00'
---

5 月は充実した一月だった。せっかくだし記録に残しておきたいので、もう 6 月に入って一週間が経とうとしているが、一ヶ月間を振り返ってみる。

## ゴールデンウィーク

今年は 10 連休にした。もはや年末年始休暇よりも長いし、暇を持て余していた。
たまの長期休暇だしめちゃくちゃ生産的な活動をしようと試みていたが、実際は家から出ずにゲームしかしていなかった。
ワーケーション的なことをやろうとしたが、思い立ったのは GW 一週間前あたりで、良さげな宿がなく断念した。次の長期休暇は前もって行動したい。
とはいえゲーム三昧はそれはそれで楽しかったので特に後悔はしてない。なんならもっとゲームしたい。

## 登壇

ここ最近は登壇意欲が高いが、5 月はさらに増していろいろやっていた。

- 司会 @ [【初心者歓迎】登壇者と攻略するRubyKaigi 2024【プロも歓迎】 - connpass](https://smartbank.connpass.com/event/313812/)
- [The Journey of rubocop-daemon into RuboCop - Speaker Deck](https://speakerdeck.com/fohte/the-journey-of-rubocop-daemon-into-rubocop) @ [LT - RubyKaigi 2024](https://rubykaigi.org/2024/presentations/lt/)
- [Datadog Logs を活用して SLO 監視基盤を構築する - Speaker Deck](https://speakerdeck.com/fohte/datadog-logs-wohuo-yong-site-slo-jian-shi-ji-pan-wogou-zhu-suru) @ [Japan Datadog User Group Meetup#4 - connpass](https://datadog-jp.connpass.com/event/317091/)
- [RubyKaigi で LT 初登壇したきっかけと感想 - Speaker Deck](https://speakerdeck.com/fohte/rubykaigi-te-lt-chu-deng-tan-sitakitukaketogan-xiang) @ [RubyKaigi 2024事後勉強会 - connpass](https://smarthr.connpass.com/event/319010/)

今月だけでなく今年は頻繁に登壇していることもあり、だいぶ登壇慣れしてきた。

登壇のモチベーションやメリットは RubyKaigi 2024 事後勉強会でも話したが、コミュニティに認知してもらえることや自分の発信に対するフィードバックがもらえることが特に嬉しいしありがたい。一連の rubocop-daemon 関連の発表では多くの人に共感してもらえたと思っているし、直近業務で難産だった Datadog Logs 活用の話では同じことをやられていた方から共感してもらえたり、新たなアイデアをもらえたりした。

また、RubyKaigi で参加だけでなく LT までできたことはとても光栄に感じている。あの大規模な会場で話せたことは自信に繋がった。貴重な経験になった。次回も発表したいし KaigiEffect でなんらかをやっていきたい。

## ゲーム

### モンスターハンターライズ: サンブレイク

<CardLink href="https://store.steampowered.com/app/1446780/MONSTER_HUNTER_RISE/?l=japanese" />

GW でセールをしていたので、友人を誘ってやり始めた。今もまだやっていて、MR 80 くらいまで進んだ。

モンハンは PS4 でプレーしたワールドぶりなのでかなり久々感がある。
ライズははじめ Switch でリリースされていたこともあり、さすがにグラフィックやフレームレート的に厳しいと感じてプレーしていなかったのだが、Steam 版がリリースされて気になっていた。
いまは UWQHD 144 fps でプレーしていてとても満足している。ヌルヌルサクサクだしグラフィックも感嘆するほど。

アクション性が特に満足度高い。翔虫がとにかく良くて、これ無しのモンハンにはもう戻れない。移動が縦にも横にも快適になるし、鉄蟲糸技 (翔虫を使った攻撃技) も回避しながら攻撃できたりして楽しい。

武器は弓を使っている。弓は見た目もいいし使用感も良く気に入っている。過去作ではハンマーメインで弓がサブだったけど、ライズでは弓しか使っていない。
今作は弓が強い (らしい) というのもあるが、入れ替え技の身躱し矢斬りが火力を出しつつ回避の楽しさもあって特に面白い。以前は回避ランサーも好きだった [^1] し、それと似たような楽しさがある。
スキルで狂化奮闘が出てからは特に強くて楽しい。無限スタミナだし、ワンパン乙みたいなこともなくなってとにかく快適。無限に身躱し矢斬りで攻撃しながら回避しつつ溜め 4 剛射を打ち続けられて、火力は出るし回避はいつでもできるし操作感が著しく改善された。スタミナ無限じゃないモンハンにはもう戻れない。

[^1]: MHF でベルキュロス相手に極長ランスで回避しながら突くのめちゃくちゃ楽しかったし、またやりたい。

そんな感じで、失った少年の心を取り戻すかの勢いでかなりハマっている。対人でもないからストレスが溜まらないのも良いポイント。

### Sixtar Gate: STARTRAIL

<CardLink href="https://store.steampowered.com/app/1802720/_/?l=japanese" />

上から降ってくるタイプの音ゲー。BEMANI 機種でいうと SDVX が一番近い。ツマミがなく、白鍵盤が 5 つある SDVX。

beatmania IIDX をやらなくなって久しいが、音ゲーはたまにやりたくなるので、最近はよくこれをやっている。家でぱっとできてかつ IIDX をある程度やっていた人でも楽しめるくらいの難易度の音ゲーとしてちょうど良い。

かわいらしいデザインとは裏腹に音ゲーマーでもしっかりと楽しめるちゃんとした音ゲーなので、ぜひやってほしい。
サウンドディレクターと譜面デザイナーが Sound Souler 氏なので、分かる人には音ゲーとしてちゃんとしていることが分かると思う。曲も某イベントの上位曲が入っていたり、譜面も最高難易度はしっかり難しくて、ちゃんと音ゲーしてる。

目指せ全 PB (無理)。まずは全 SS から。いまは難易度 17 の SS に苦戦しているところで、S で精一杯。

### Antimatter Dimensions

<CardLink href="https://store.steampowered.com/app/1399720/Antimatter_Dimensions/" />

[人生で最も時間を破壊された放置ゲー|zubu](https://note.com/zubububu/n/n62602227c1cc) を読んで軽い気持ちで始めてみたらめちゃくちゃ時間を溶かしてしまっている放置ゲー。
Cookie Clicker に 10 倍くらい「数を増やす」要素を付け足したゲーム。1e100 とかそういうレベルではなく 1e1e9 とかになるくらい桁が無尽蔵に増えていく。
Cookie Clicker にハマった人にはおすすめしたいゲーム。放置ゲーとはいいつつ適度にクリックする必要があり、時間は溶けるので注意。

いまは Reality を解禁して 1e40 RM くらい。Reality は後から大型アップデートとして追加されただけあって、頻繁に新しい要素が追加されていくのが難しくもあり面白い。とりあえずクリア (クリアという概念はあるんだろうか) まではやりたい。

### Vampire Survivors

<CardLink href="https://store.steampowered.com/app/1794680/Vampire_Survivors/?l=japanese" />

無双ゲー。2 年前くらいに流行っていたので知っている人も多そう。

その流行っていた 2 年前にもプレーしていたのだが、最近また手を出してみたら要素が 2 年前の 10 倍くらい増えていた。死神を倒すために作られたような進化武器が追加されていたりする。

なぜ今になってまたハマったかというと、Antimatter Dimensions で実績を解除していくのが楽しくて Steam 実績を集めるのが趣味になってしまったから。Vampire Survivors は 1 ゲームで 1 実績解除くらいできるので楽しい。本来の楽しみ方とは違うけど、実績解除ゲーとして楽しんでいる。

## 映画

### デッドデッドデーモンズデデデデデストラクション

<Mastodon url="https://social.fohte.net/@fohte/112500420724317611" />

前編後編見た。漫画を全巻読破して感慨深い気持ちになっていたときに、ちょうど映画が上映されていることを知ったので見に行った。

映画は漫画版が忠実に映像化されているという印象でとても良かった。特に音の臨場感が良く、漫画では味わえない壮大さを感じられた。

ポストアポカリプス系の物語が好きなので、デデデデも例に漏れず刺さった。浅野いにおさんの作品はデデデデが初めてで、漫画の 1, 2 巻あたりはハイテンションについていけずなんだこれとなっていたが、読み進めていくとどんどん深みが出て面白くなった。ネタバレになるので書くことが限られるのだが、絶望的な展開が晴れていくような物語が好きな人にはおすすめ。

## 漫画

### それでも町は廻っている

<CardLink href="https://amzn.to/3V4nwxq" />

同じ作者の『[天国大魔境](https://amzn.to/4e6XW3r)』がとにかく面白く、同じ作者であれば絶対に面白いだろうと信じて読んだらやはり期待通りとても面白かった。
『天国大魔境』はポストアポカリプス系でくすっと笑えるコメディ要素が入った漫画なのだが、この作品はコメディ全振りでよかった。ギャグのテンポが早くて心地よいし、シュールな笑いが持続する感じがとてもよい。
読破してしまったけど、無限に続いてほしい作品だった。

### おやすみプンプン

<CardLink href="https://amzn.to/3Vf9Z62" />

デデデデの作者である浅野いにおさんの漫画。これはポストアポカリプス系ではなく、デデデデに似た謎のテンションで始まり話が進むにつれて深みが出てくるタイプの作品だった。
救いのない鬱展開だったが面白く読めた。個人的に好きな展開としては鬱展開から這い上がっていく物語が好きなのだが、この作品は這い上がらずにそのまま鬱展開のまま進んでいくのがなかなか味わえない良い意味での気味悪さがあった。

結局最後まで訳が分からない部分もあったのだが、以下の考察記事を読んでかなり納得感があった。読破した人は読んでほしい。

<CardLink href="https://gumichoko.hatenablog.com/entry/2019/07/26/230937" />

## 最後に

ブログは書きたいけど適切な粒度が見つからない、見つかっても書くハードルが高いなと思っていたので、月次振り返りはちょうどいいかもしれない。来月は今月分も振り返っていきたい。
その振り返るためのネタができるくらいには人生をやっていきたい所存。KaigiEffect もやっていきたい。
Loading

0 comments on commit ed68174

Please sign in to comment.