参考
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);