LGTMeow からネコチャン🐈をさっと選んで Pull Request に貼り付けるための Raycast Extension を作る

関連:

LGTMeow の API を叩いて画像を Grid に表示する
APIの結果を元に表示できた
import { useState } from "react";
import { Grid } from "@raycast/api";
import { useFetch } from "@raycast/utils";
type Item = {
id: number;
imageUrl: string;
}
export default function Command() {
const [items, setItems] = useState<Item[]>([]);
const { isLoading } = useFetch<Item[]>(
"https://lgtmeow.com/api/lgtm-images",
{
onData: (data) => {
console.log('onData:', data);
const newItems: Item[] = [];
for(const item of data) {
newItems.push(item)
}
setItems(newItems);
}
}
);
return (
<Grid
isLoading={isLoading}
inset={Grid.Inset.Zero}
filtering={false}
navigationTitle="Choose a nekochan"
searchBarPlaceholder=""
>
{items.map((item) => (
<Grid.Item key={item.id} content={item.imageUrl} />
))}
</Grid>
);
}

選択したネコチャンのURLを Clipboard にコピーする
クリップボードのAPI
Gridでの選択したものを取得するには onSelectionChange
プロパティ
選択したものを Clipboard にコピーする場合は UI コンポーネントとしての Action を使うらしい
{items.map((item) => (
<Grid.Item
id={item.imageUrl}
key={item.id}
content={item.imageUrl}
actions={
<ActionPanel title="Choose a favorite neko-chan">
<Action.CopyToClipboard title="Copy URL" content={item.imageUrl} />
</ActionPanel>
}
/>
))}
選択したネコチャンのURLをクリップボードにコピーできた

選択したネコチャンのURLを Markdown 形式の画像としてコピーできるようにする
本家のコピー形式は
[](https://lgtmeow.com)
これに合わせる


アクティブなアプリケーションに貼り付けるところまでやりたい
onCopy
で Clipboard.paste
呼び出せば行ける気がする
Pastes text or a file to the current selection of the frontmost application.
https://developers.raycast.com/api-reference/user-interface/actions#action.copytoclipboard

<Action.CopyToClipboard
title="Copy URL"
content={formatImageMarkdown(item.imageUrl)}
onCopy={Clipboard.paste}
/>
これで行けた

アプリケーションへの貼り付けは別の Action とする
無条件に貼り付けまでされると困る場面もありそうなので、enter
は Clipboard へのコピーまでとして、 cmd
+ enter
で貼り付けまで行うように変更する。
https://developers.raycast.com/api-reference/user-interface/action-panel
he first and second action become the primary and secondary action. They automatically get the default keyboard shortcuts assigned. In List, Grid, and Detail, this is ↵ for the primary and ⌘ ↵ for the secondary action.
Action が複数あったときに、デフォルトでそのようなショートカットが割り当てられる

これで行けた
<ActionPanel title="Choose a favorite neko-chan">
<Action.CopyToClipboard
title="Copy URL"
content={formatImageMarkdown(item.imageUrl)}
/>
<Action.CopyToClipboard
title="Copy URL & Paste to Frontmost App"
content={formatImageMarkdown(item.imageUrl)}
onCopy={Clipboard.paste}
/>
</ActionPanel>

できたもの

↓は調査/実装の途中経過など

LGTMeow の一覧画像の取得方法を調べる
fetchLgtmImagesUrl(appBaseUrl)
でURLを組み立てていそう
挙動見た感じ SSR っぽい?
となると API は公開されてないか

画像を選択する UI を検討する
Grid がよさそう

なんとなくそれっぽいもの

とりあえず現状のコード
とりあえず動くコード
import { useEffect, useState } from "react";
import { Grid } from "@raycast/api";
import { useFetch } from "@raycast/utils";
import { parseDocument } from "htmlparser2";
import { getElementsByTagName } from "domutils";
const DUMMY_BASE_URL = 'https://example.com';
export default function Command() {
const [items, setItems] = useState(new Set<string>([]));
const { isLoading, data } = useFetch<string>("https://lgtmeow.com/");
useEffect(() => {
if (!isLoading && data) {
const dom = parseDocument(data);
const images = getElementsByTagName("img", dom);
const newItems = new Set<string>([]);
for (const image of images) {
const imgSrc = image.attribs.src;
const assetUrlParam = new URL(imgSrc, DUMMY_BASE_URL).searchParams.get("url");
if (assetUrlParam) {
newItems.add(assetUrlParam);
}
}
setItems(newItems);
}
}, [isLoading, data])
return (
<Grid
isLoading={isLoading}
inset={Grid.Inset.Zero}
filtering={false}
navigationTitle="Choose a nekochan"
searchBarPlaceholder=""
>
{[...items].map((image) => (
console.log("image: ", image),
<Grid.Item key={image} content={image} />
))}
</Grid>
);
}
API あったので書き直す

キャッシュを一定時間経過後に破棄する
どうも useFetch の結果がキャッシュされているっぽいので、一定期間過ぎたらキャッシュ破棄したい
あるいは手動でリロードしたい

useCachedState
useFetch
は useCachedState
のプロパティを受け取れるようになっている
-
key
はキャッシュのキーになっているので、ここを時間単位で変化する値にすることで期待する結果が得られそう

key
は options に含まれていないので useFetch
に渡すことはできなさそう
revalidate
使うか

いや、どうやらサーバ側でキャッシュされてそうだな

Web で [Cats Refresh] 押したときと同等の Action を用意したい

あれ?普通に API あった
GET https://lgtmeow.com/api/lgtm-images
でランダムに画像のURLが JSON で取得できる
[
{
"id": 35,
"imageUrl": "https://lgtm-images.lgtmeow.com/2021/03/16/22/e3fe2715-4155-45fc-b1cc-916db866f78f.webp"
},
{
"id": 138,
"imageUrl": "https://lgtm-images.lgtmeow.com/2021/11/16/22/ae21402b-64a6-4697-bfac-06b9766f75af.webp"
},
{
"id": 522,
"imageUrl": "https://lgtm-images.lgtmeow.com/2023/08/25/15/9e84fe28-81dc-4394-a9ca-4fb085919352.webp"
},
{
"id": 568,
"imageUrl": "https://lgtm-images.lgtmeow.com/2023/11/07/09/5a479d0a-83e2-4af3-b266-bf8a8073e6ca.webp"
},
{
"id": 582,
"imageUrl": "https://lgtm-images.lgtmeow.com/2023/12/11/23/19919a32-e472-4d3a-9074-25b5b5ed32e0.webp"
},
{
"id": 603,
"imageUrl": "https://lgtm-images.lgtmeow.com/2024/01/23/17/3fb91863-0544-4d7b-8615-3e6327dd14dc.webp"
},
{
"id": 694,
"imageUrl": "https://lgtm-images.lgtmeow.com/2024/06/25/14/2b95cd5f-4ecc-4812-9e84-f16622235e96.webp"
},
{
"id": 753,
"imageUrl": "https://lgtm-images.lgtmeow.com/2024/09/26/11/c4805315-3e25-4977-973c-4822e2852027.webp"
},
{
"id": 756,
"imageUrl": "https://lgtm-images.lgtmeow.com/2024/09/27/15/863abb89-7c10-41d1-b4cf-ef1500a5adc3.webp"
}
]