はじめに
こんにちは。夏休みは新潟からフェリーで北海道に行く予定を立てている@yug1224です。
最近はTwitterの突発的な仕様変更により、Twitter以外の分散型SNSも注目されるようになってきましたね。自分もちょうどBlueskyの招待コードをいただいたので登録して遊んでいます。
今回はGitHubでStarを付けたらBlueskyに投稿するプログラムを作ってみたので紹介します!
Blueskyとは?🤔
まずそもそもBlueskyとは何か?
BlueskyとはTwitterの創業者であるジャック・ドーシー氏が支援する分散型SNSであり、現在はプライベートベータ中のサービスですね。
今のBlueskyはIT系の人が多く、2010年前後のTwitterのような雰囲気もあり、個人的には居心地の良さを感じていますw
ざっくりと知るならギズモードの記事がわかりやすいかなと思います。
Blueskyに投稿するプログラムを書く💻
BlueskyはAT Protocolと呼ばれるプロトコルを使って情報のやり取りを行いますが、すでにatprotoというライブラリが公開されているので、まずはこれを使ってBlueskyにテキストを投稿するプログラムを実装していきます。
import 'https://deno.land/std@0.193.0/dotenv/load.ts'
import AtprotoAPI from 'npm:@atproto/api'
// Blueskyに接続
const { BskyAgent, RichText } = AtprotoAPI
const service = 'https://bsky.social'
const agent = new BskyAgent({ service })
const identifier = Deno.env.get('BLUESKY_IDENTIFIER') || ''
const password = Deno.env.get('BLUESKY_PASSWORD') || ''
await agent.login({ identifier, password })
// Blueskyに投稿
const postObj = {
$type: 'app.bsky.feed.post',
text: 'Hello, AT Protocol!',
}
const result = await agent.post(postObj)
console.log(result)
agent.login()
をしてからagent.post()
をするだけ!簡単ですね!
リンクカード情報を投稿に含める🃏
次は一気に実装していきます。
Blueskyで投稿にリンクカードを表示するためには、リンクカードに表示する情報を投稿に含める必要があるので、OGP情報を事前に取得します。
GitHubプロフィールリンクの末尾に.atom
を付けることで自身のActivityをRSSとして取得できるので、これを利用して情報を集めていきます。例: https://github.com/yug1224.atom
import 'https://deno.land/std@0.193.0/dotenv/load.ts'
import { Image } from 'https://deno.land/x/imagescript@1.2.15/mod.ts'
import { parseFeed } from 'https://deno.land/x/rss@0.6.0/mod.ts'
import AtprotoAPI from 'npm:@atproto/api'
import ogs from 'npm:open-graph-scraper'
// rss feedから最新のスターを付けた記事リストを取得
const getStarredItemList = async () => {
const response = await fetch(Deno.env.get('RSS_URL') || '')
const xml = await response.text()
const feed = await parseFeed(xml)
const foundList = feed.entries.reverse().filter((item) => {
return (
new Date(new Date(Deno.env.get('LAST_EXECUTION_TIME') || '')) <
new Date(item.published) &&
new RegExp('starred', 'g').test(item.title.value)
)
})
return foundList
}
const starredItemList = await getStarredItemList()
console.log(starredItemList)
// 対象がなかったら終了
if (!starredItemList.length) {
console.log('not found starred item')
Deno.exit(0)
}
// Blueskyに接続
const { BskyAgent, RichText } = AtprotoAPI
const service = 'https://bsky.social'
const agent = new BskyAgent({ service })
const identifier = Deno.env.get('BLUESKY_IDENTIFIER') || ''
const password = Deno.env.get('BLUESKY_PASSWORD') || ''
await agent.login({ identifier, password })
// 取得した記事リストをループ処理
for await (const starredItem of starredItemList) {
// 投稿予定のテキストを作成
const text = `${starredItem.title.value}\n${starredItem.links[0].href}`
const pattern =
/https?:\/\/[-_.!~*\'()a-zA-Z0-9;\/?:\@&=+\$,%#\u3000-\u30FE\u4E00-\u9FA0\uFF01-\uFFE3]+/g
const [url] = text.match(pattern) || ['']
// URLからOGPの取得
const getOgp = async (url: string) => {
const { result } = await ogs({ url })
const ogImage = result.ogImage?.at(0)
const response = await fetch(ogImage?.url || '')
const buffer = await response.arrayBuffer()
const image = await Image.decode(buffer)
const resizedImage = await image
.resize(800, Image.RESIZE_AUTO)
.encodeJPEG(80)
return {
url: ogImage?.url || '',
type: ogImage?.type || '',
description: result.ogDescription || '',
title: result.ogTitle || '',
image: resizedImage,
}
}
const og = await getOgp(url)
// 画像をアップロード
const uploadedImage = await agent.uploadBlob(og.image, {
encoding: 'image/jpeg',
})
// Blueskyに投稿
const rt = new RichText({ text })
await rt.detectFacets(agent)
console.log(rt.text, rt.facets)
const postObj = {
$type: 'app.bsky.feed.post',
text: rt.text,
facets: rt.facets,
embed: {
$type: 'app.bsky.embed.external',
external: {
uri: url,
thumb: {
$type: 'blob',
ref: {
$link: uploadedImage.data.blob.ref.toString(),
},
mimeType: uploadedImage.data.blob.mimeType,
size: uploadedImage.data.blob.size,
},
title: og.title,
description: og.description,
},
},
}
const result = await agent.post(postObj)
console.log(result)
}
OGP情報を元にリンクカードも表示できました!簡単ですね!
GitHub Actionsで定期実行する⏰
最後にGitHub Actionsで定期実行するように設定します。
BLUESKY_IDENTIFIER
BLUESKY_PASSWORD
はBlueskyのSettingsから取得して、secretsに登録しておきます。
name: Star to Bluesky
on:
schedule:
# 5分ごとに実行
- cron: '0/5 * * * *'
workflow_dispatch:
jobs:
star-to-bluesky:
runs-on: ubuntu-latest
steps:
# リポジトリのチェックアウト
- name: Checkout
uses: actions/checkout@v4
# 前回の実行時刻を環境変数に設定
- name: Get Last Execution Time
id: last-execution
run: |
url="https://api.github.com/repos/yug1224/star-to-bluesky/actions/workflows/star-to-bluesky.yml/runs?status=success&per_page=1"
echo "LAST_EXECUTION_TIME=$(curl -fsSL "$url" | jq -r '.workflow_runs[0].updated_at')" >> $GITHUB_ENV
# Denoのセットアップ
- name: Setup Deno
uses: denoland/setup-deno@v1
with:
deno-version: v1.x
# Denoの実行
- name: Deno Run
run: deno run --allow-read --allow-env --allow-net main.ts
env:
BLUESKY_IDENTIFIER: ${{secrets.BLUESKY_IDENTIFIER}}
BLUESKY_PASSWORD: ${{secrets.BLUESKY_PASSWORD}}
RSS_URL: ${{secrets.RSS_URL}}
だいたい5分ごとに定期実行できました!簡単ですね!
最後に🎉
今回はGitHubでStarを付けたらBlueskyに投稿するプログラムを作成しました!
下記リポジトリで公開しているので、是非動かしてみてください!🙌
Blueskyアカウントへのリンクも載せておきます。もし良ければフォローお願いします!🎉
参考記事📚
下記記事を参考にさせていただきました!ありがとうございました!