🦔

AI エージェント Codex さんが Bluesky に投稿する Julia のクライアントアプリを作ってくれたようです

に公開

本日は

Julia アドベントカレンダーです.

数年前,Twitter というものがあった時の時代ですが,Plots.jl で作った画像,GIFを投稿するツール (TweetPlots.jl) を作っていました.

https://zenn.dev/terasakisatoshi/articles/662ef47cc13f0c

今日の X とやらでどうするのかわからなかったので今は放置しています.
今は Bluesky ですよ.#JuliaLang のタグをフォローしていれば静かで・穏やかです.

作ったもの

Bluesky で同様のことができるようになりたかったので AI エージェント Codex に作らせました.
下記のリポジトリをクローンして読ませて

「文字を投稿できるようにしたい」旨のプロンプトを入力させ,デバッグさせながら3時間ほどで完成させました.成果物は下記のリンクにあります.

https://github.com/AtelierArith/BlueskyClient.jl

用意するもの

  • Julia v1.12 以上
    • 投稿するネタ
  • Bluesky にログインするときのメールアドレスとパスワード.各々 BSKY_HANDLE, BSKY_PASSWORD という名前で環境変数に格納しておきます.

使い方

文字の投稿

send_post 関数で投稿できます.

using BlueskyClient
using DotEnv; DotEnv.load!()

client = Client()  # defaults to https://bsky.social

# Prefer App Passwords with DM scope when accessing real accounts.
session = login!(client;
    identifier = ENV["BSKY_HANDLE"],
    password   = ENV["BSKY_PASSWORD"],
)

post_text = "I'm making BlueskyClient.jl now ♩"
@info "Sending text post to Bluesky"
post = send_post(client, post_text)
@info "Text post sent successfully" uri=post.uri
println("New record URI: ", post.uri)

画像の投稿

send_image 関数を使います.ALT 付きで投稿ができます.

using BlueskyClient
using DotEnv; DotEnv.load!()

client = Client()
login!(client; identifier=ENV["BSKY_HANDLE"], password=ENV["BSKY_PASSWORD"])

image_path = "screenshot.png"
@info "Loading image fixture" path=image_path
img_bytes = read(image_path)

@info "Sending image post to Bluesky"
post = send_image(
    client,
    "Screenshot from today's build ✅",
    img_bytes,
    "BlueskyClient.jl terminal output showing the new client API", # ALT
    mime_type="image/png",
)
@info "Image post sent successfully" uri=post.uri

GIF, MP4 の投稿

GIF は MP4 に変換して投稿します.下記のスクリプトで Makie.jl のdemoを投稿することができます.

using BlueskyClient
using DotEnv
using GLMakie

DotEnv.load!()

# https://docs.makie.org/stable/
using GLMakie

Base.@kwdef mutable struct Lorenz
    dt::Float64 = 0.01
    σ::Float64 = 10
    ρ::Float64 = 28
    β::Float64 = 8/3
    x::Float64 = 1
    y::Float64 = 1
    z::Float64 = 1
end

function step!(l::Lorenz)
    dx = l.σ * (l.y - l.x)
    dy = l.x * (l.ρ - l.z) - l.y
    dz = l.x * l.y - l.β * l.z
    l.x += l.dt * dx
    l.y += l.dt * dy
    l.z += l.dt * dz
    Point3f(l.x, l.y, l.z)
end

attractor = Lorenz()

points = Point3f[]
colors = Int[]

set_theme!(theme_black())

fig, ax, l = lines(points, color = colors,
    colormap = :inferno, transparency = true,
    axis = (; type = Axis3, protrusions = (0, 0, 0, 0),
              viewmode = :fit, limits = (-30, 30, -30, 30, 0, 50)))

mktempdir() do dir
    video_path = joinpath(dir, "lorenz.mp4")
    @info "Recording Lorenz attractor animation" path=video_path
    record(fig, video_path, 1:120) do frame
        for i in 1:50
            push!(points, step!(attractor))
            push!(colors, frame)
        end
        ax.azimuth[] = 1.7pi + 0.3 * sin(2pi * frame / 120)
        Makie.update!(l, arg1 = points, color = colors) # Makie 0.24+
        l.colorrange = (0, frame)
    end
    @info "Completed Lorenz animation recording"

    client = Client()
    login!(client; identifier=ENV["BSKY_HANDLE"], password=ENV["BSKY_PASSWORD"])

    video_bytes = read(video_path)
    upload = upload_blob(client, Vector{UInt8}(video_bytes); content_type="video/mp4")

    embed = Dict(
        "\$type" => "app.bsky.embed.video",
        "video" => upload["blob"],
        "alt" => get(ENV, "BSKY_VIDEO_ALT", "MP4 sample posted from BlueskyClient.jl"),
        "aspectRatio" => Dict("width" => 16, "height" => 9),
    )

    @info "Sending MP4 post to Bluesky"
    post = send_post(client, "Post with MP4 attachment 🎬"; embed=embed)
    @info "MP4 post sent successfully" uri=post.uri
end

これで可視化ボットを作ることができますね.

Julia は(他の言語にあるが,Juliaにはないという意味で)ライブラリが少ないと言われますが,LLMの時代であれば,公式実装があればそれを元に Julia パッケージが作れるようになってきました.使いたいと感じる人間のヤル気と情熱があればなんとかなりそうです.

Discussion