0
0

bluesky api php で post

Last updated at Posted at 2024-06-27

参考
https://mgng.mugbum.info/1699

blue sky で post したい

ハッシュタグは使えないので投稿時に削除する

1 アプリパスワードを作成
https://bsky.app/settings/app-passwords

2 パスワードを設定

.env
BLUESKY_APP_PASSWORD = xxx-xxxx-xxx-xxx
app.php
'bluesky_app_password' => env('BLUESKY_APP_PASSWORD')

続いてモデル

bluesky.php

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Carbon\Carbon;

class Bluesky extends Model
{
    public $jwt;
    public $handle;

    public function __construct($handle, $password)
    {
        $this->handle = $handle;
        $this->jwt = $this->getJwt($handle, $password);
        parent::__construct();
    }

    private function getJwt($handle, $password)
    {
        $ch = curl_init("https://bsky.social/xrpc/com.atproto.server.createSession");
        curl_setopt_array($ch, [
            CURLOPT_CONNECTTIMEOUT => 10,
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
            CURLOPT_HTTPHEADER => ["Content-Type: application/json"],
            CURLOPT_POSTFIELDS => json_encode([
                "identifier" => $handle,
                "password" => $password,
            ]),
        ]);
        $response = curl_exec($ch);
        curl_close($ch);

        $responseJson = json_decode($response, true);
        if (isset($responseJson["accessJwt"])) {
            return $responseJson["accessJwt"];
        } else {
            throw new \Exception("Failed to obtain JWT: " . $response);
        }
    }


    //    ハッシュタグを削除
    public static function removeHashTag($data)
    {
        preg_match_all('/#(w*[一-龠_ぁ-ん_ァ-ヴーa-zA-Za-zA-Z0-9]+|[a-zA-Z0-9_]+|[a-zA-Z0-9_]w*)/',  $data, $matches);
        foreach ($matches[0] as $s) {
            $data = str_replace($s,'',$data);
        }

        return $data;
    }


    public function post($text, $imagePath = null, $link = null)
    {
        $imageUri = $imagePath ? $this->uploadImage($imagePath) : null;

        $text = $this->removeHashTag($text);



        $record = [
            "\$type" => "app.bsky.feed.post",
            "text" => $text,
            "createdAt" => Carbon::now()->format('c'),
        ];

        if ($imageUri) {
            $record['embed'] = [
                '$type' => 'app.bsky.embed.images',
                'images' => [
                    [
                        'image' => $imageUri,
                        'alt' => 'Image description'
                    ]
                ]
            ];
        }

        $facets = $this->createFacets($text, $link);

        if (!empty($facets)) {
            $record['facets'] = $facets;
        }

        $ch = curl_init("https://bsky.social/xrpc/com.atproto.repo.createRecord");
        curl_setopt_array($ch, [
            CURLOPT_CONNECTTIMEOUT => 10,
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
            CURLOPT_HTTPHEADER => [
                "Content-Type: application/json",
                "Authorization: Bearer {$this->jwt}",
            ],
            CURLOPT_POSTFIELDS => json_encode([
                "repo" => $this->handle,
                "collection" => "app.bsky.feed.post",
                "record" => $record,
            ]),
        ]);

        $response = curl_exec($ch);
        curl_close($ch);

        return json_decode($response, true);
    }

    private function createFacets($text, $link)
    {
        $facets = [];

        // Handle links
        if ($link) {
            $linkStart = strpos($text, $link);
            $linkEnd = $linkStart + strlen($link);

            $facets[] = [
                'index' => [
                    'byteStart' => $linkStart,
                    'byteEnd' => $linkEnd
                ],
                'features' => [
                    [
                        '$type' => 'app.bsky.richtext.facet#link',
                        'uri' => $link
                    ]
                ]
            ];
        }

        // Handle hashtags
        preg_match_all('/#(\w+)/', $text, $matches, PREG_OFFSET_CAPTURE);
        foreach ($matches[0] as $match) {
            $hashtag = $match[0];
            $hashtagStart = $match[1];
            $hashtagEnd = $hashtagStart + strlen($hashtag);

            $facets[] = [
                'index' => [
                    'byteStart' => $hashtagStart,
                    'byteEnd' => $hashtagEnd
                ],
                'features' => [
                    [
                        '$type' => 'app.bsky.richtext.facet#tag',
                        'tag' => substr($hashtag, 1) // remove the hash symbol
                    ]
                ]
            ];
        }

        return $facets;
    }

    private function uploadImage($imagePath)
    {
        $imageData = file_get_contents($imagePath);
        $mime = mime_content_type($imagePath);

        $ch = curl_init("https://bsky.social/xrpc/com.atproto.repo.uploadBlob");
        curl_setopt_array($ch, [
            CURLOPT_CONNECTTIMEOUT => 10,
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
            CURLOPT_HTTPHEADER => [
                "Content-Type: $mime",
                "Authorization: Bearer {$this->jwt}",
            ],
            CURLOPT_POSTFIELDS => $imageData,
        ]);

        $response = curl_exec($ch);
        $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($httpcode == 200) {
            $responseJson = json_decode($response, true);
            return $responseJson['blob'] ?? null;
        } else {
            throw new \Exception("Failed to upload image: HTTP $httpcode - $response");
        }
    }
}




HogesController.php


$bluesky = new Bluesky("your.sky.social",config('app.bluesky_app_password'));
$res = $bluesky->post("APIからメッセージを\n投稿するよ".time());

はい、たったこれだけでpostできます。

画像付き、リンク付きでアップロード

そのままだとリンクが有効にならない。
以下のコードでリンクを有効にできる。

bluesky.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Carbon\Carbon;

class Bluesky extends Model
{
    public $jwt;
    public $handle;

    public function __construct($handle, $password)
    {
        $this->handle = $handle;
        $this->jwt = $this->getJwt($handle, $password);
        parent::__construct();
    }

    private function getJwt($handle, $password)
    {
        $ch = curl_init("https://bsky.social/xrpc/com.atproto.server.createSession");
        curl_setopt_array($ch, [
            CURLOPT_CONNECTTIMEOUT => 10,
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
            CURLOPT_HTTPHEADER => ["Content-Type: application/json"],
            CURLOPT_POSTFIELDS => json_encode([
                "identifier" => $handle,
                "password" => $password,
            ]),
        ]);
        $response = curl_exec($ch);
        curl_close($ch);

        $responseJson = json_decode($response, true);
        if (isset($responseJson["accessJwt"])) {
            return $responseJson["accessJwt"];
        } else {
            throw new \Exception("Failed to obtain JWT: " . $response);
        }
    }

    public function post($text, $imagePath = null, $link = null)
    {
        $imageUri = $imagePath ? $this->uploadImage($imagePath) : null;

        $record = [
            "\$type" => "app.bsky.feed.post",
            "text" => $text,
            "createdAt" => Carbon::now()->format('c'),
        ];

        if ($imageUri) {
            $record['embed'] = [
                '$type' => 'app.bsky.embed.images',
                'images' => [
                    [
                        'image' => $imageUri,
                        'alt' => 'Image description'
                    ]
                ]
            ];
        }

        if ($link) {
            $linkStart = strpos($text, $link);
            $linkEnd = $linkStart + strlen($link);

            $record['facets'] = [
                [
                    'index' => [
                        'byteStart' => $linkStart,
                        'byteEnd' => $linkEnd
                    ],
                    'features' => [
                        [
                            '$type' => 'app.bsky.richtext.facet#link',
                            'uri' => $link
                        ]
                    ]
                ]
            ];
        }

        $ch = curl_init("https://bsky.social/xrpc/com.atproto.repo.createRecord");
        curl_setopt_array($ch, [
            CURLOPT_CONNECTTIMEOUT => 10,
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
            CURLOPT_HTTPHEADER => [
                "Content-Type: application/json",
                "Authorization: Bearer {$this->jwt}",
            ],
            CURLOPT_POSTFIELDS => json_encode([
                "repo" => $this->handle,
                "collection" => "app.bsky.feed.post",
                "record" => $record,
            ]),
        ]);

        $response = curl_exec($ch);
        curl_close($ch);

        return json_decode($response, true);
    }

    private function uploadImage($imagePath)
    {
        $imageData = file_get_contents($imagePath);
        $mime = mime_content_type($imagePath);

        $ch = curl_init("https://bsky.social/xrpc/com.atproto.repo.uploadBlob");
        curl_setopt_array($ch, [
            CURLOPT_CONNECTTIMEOUT => 10,
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
            CURLOPT_HTTPHEADER => [
                "Content-Type: $mime",
                "Authorization: Bearer {$this->jwt}",
            ],
            CURLOPT_POSTFIELDS => $imageData,
        ]);

        $response = curl_exec($ch);
        $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($httpcode == 200) {
            $responseJson = json_decode($response, true);
            return $responseJson['blob'] ?? null;
        } else {
            throw new \Exception("Failed to upload image: HTTP $httpcode - $response");
        }
    }
}

HogesController.php

$bluesky = new Bluesky("your.bsky.social",config('app.bluesky_app_password'));

$text = 'こんにちは、まじでいいよ!ママリッチです: https://mama-rich.net';
$link = 'https://mama-rich.net';


//画像なし、リンクあり
$response = $bluesky->post($text, null, $link);

//画像、リンクあり
$img = public_path("/img/seiya.jpg");
$response = $bluesky->post($text, $img, $link);

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0