Deno_Bluesky
DenoでBlueskyにポストしたり自分の過去ポストを全取得したりするコードを作った。
私が欲しいのは投稿と過去ポスト取得だけなので、タイムラインの取得などは挑んでいない。
参考
投稿を遡って再帰的に取得するコードはこちらから。大変参考になりました。
IDとパスワードは.envファイルに記述しておく
下記のBLUESKY_IDENTIFIERは私のアカウントの場合のID
私はBLUESKY_IDENTIFIERとBLUESKY_PASSWORDにしているが、なんでもいいと思う
code:.env
BLUESKY_IDENTIFIER=noratetsu.bsky.social
BLUESKY_PASSWORD=hoge
以下bsky.tsというファイルに書くとする
冒頭部分
ログイン処理をする
code:bsky.ts
import AtprotoAPI from "npm:@atproto/api";
const { BskyAgent } = AtprotoAPI;
const agent = new BskyAgent({ service });
const identifier = Deno.env.get("BLUESKY_IDENTIFIER") as string;
const password = Deno.env.get("BLUESKY_PASSWORD") as string;
await agent.login({ identifier, password });
スクリプト引数に応じて処理を変える
引数の一つ目で種類を判定、二つ目は各関数に渡す
code:bsky.ts
if (Deno.args0 === 'POST') { }
} else if (Deno.args0 === 'GET') { writeFeedData(Deno.args1); } else if (Deno.args0 === 'UPDATE') { updateFeedData(Deno.args1); }
投稿する関数
code:bsky.ts
/**
* Blueskyにテキストを投稿する
* @param text
*/
async function post(text: string) {
await agent.post({
$type: "app.bsky.feed.post",
text: text,
});
console.log('postしました');
}
自分の投稿を取得する関数
code:bsky.ts
/**
* 自分の投稿を全て取得する
* @param conditionFn 途中で処理を切り上げる条件
* @returns
*/
function getMyAllPosts(conditionFn?: (data: AtprotoAPI.AppBskyFeedDefs.FeedViewPost[]) => boolean) {
return getAuthorFeed([], conditionFn);
// 再帰処理のための関数
async function getAuthorFeed(feed: AtprotoAPI.AppBskyFeedDefs.FeedViewPost[], conditionFn?: (data: AtprotoAPI.AppBskyFeedDefs.FeedViewPost[]) => boolean, cursor?: string) {
if (conditionFn && conditionFn(feed)) return feed;
const timeline = await agent.getAuthorFeed({
actor: identifier,
limit: 100,
cursor,
})
if (timeline.success) {
if (timeline.data.cursor) {
} else {
}
} else {
throw new Error('timeline fetch error:' + JSON.stringify(timeline.data));
}
}
}
取得したデータを自分に必要なデータに変換する関数
code:bsky.ts
type SavedData = {
text: string,
createdAt: string,
isRepost: boolean,
author: string,
// 実際には他にもある
}
/**
* 保存用のデータを整形する
* @param feed
* @returns
*/
function formatFeedData(feed: AtprotoAPI.AppBskyFeedDefs.FeedViewPost[]) {
feed.sort((a, b) => {
return new Date(a.post.indexedAt) > new Date(b.post.indexedAt) ? -1 : 1;
})
const records = feed.map(data => {
const obj = Object.assign(data.post.record);
delete obj.langs;
delete obj.$type;
if (data.post.author.handle === identifier) {
return obj;
} else {
obj.isRepost = true;
obj.author = data.post.author.handle;
return obj;
}
});
return records as SavedData[];
}
自分の投稿データをファイルに書き込む関数
code:bsky.ts
/**
* 全てのpostを取得してファイルに書き込む
* @param filePath
*/
async function writeFeedData(filePath = 'bsky_feed.json') {
const feed = await getMyAllPosts();
const records = formatFeedData(feed);
Deno.writeTextFileSync(filePath, JSON.stringify(records, null, '\t'));
console.log(filePath + ' に出力しました。');
}
既存のファイルと照らして差分だけ追加する関数
code:bsky.ts
/**
* 既存のデータとの差分を追加して更新する
* @param filePath
*/
async function updateFeedData(filePath = 'bsky_feed.json') {
const text = Deno.readTextFileSync(filePath);
const data = JSON.parse(text) as SavedData[];
const conditionFn = (feed: AtprotoAPI.AppBskyFeedDefs.FeedViewPost[]) => feed.some(obj => data.some(o => o.createdAt === obj.post.indexedAt));
const feed = await getMyAllPosts(conditionFn);
const filter = feed.filter(obj => !data.some(o => o.createdAt === obj.post.indexedAt));
const records = formatFeedData(filter);
data.push(...records);
data.sort((a, b) => {
return new Date(a.createdAt) > new Date(b.createdAt) ? -1 : 1;
})
Deno.writeTextFileSync(filePath, JSON.stringify(data, null, '\t'));
console.log(filePath + ' を更新しました。');
}
deno.jsonのtask設定
スクリプト引数の一つ目を入れたtaskを作っておく
code:deno.json
{
"tasks": {
"bsky": "deno run --allow-read --allow-env --allow-net bsky.ts POST",
"postBluesky": "deno run --allow-read --allow-env --allow-net bsky.ts POST",
"getBluesky": "deno run --allow-read --allow-write --allow-env --allow-net bsky.ts GET",
"updateBluesky": "deno run --allow-read --allow-write --allow-env --allow-net bsky.ts UPDATE"
}
}
使用例(CLI)
deno task bsky 本日は晴天なり
deno task getBluesky bsky.json