CCCPaste Login

BSKY JWT post w/card

 https://sl-memo.blogspot.com/

date_default_timezone_set('Asia/Tokyo');
$UAstring = "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:139.0) Gecko/20100101 Firefox/139.0";

//入力データ
$readfile = "text_data.txt";
//$readdata = explode("\n", file_get_contents($readfile));
//テスト用なので固定の配列で記述
$readdata = array(
 '001<->IMG - 3<->https://www.flickr.com/photos/153589246@N07/37182374834/<->https://live.staticflickr.com/4459/37182374834_a89ca42e10_c.jpg'
,'002<->Baby Anythings are Cute<->https://www.flickr.com/photos/dennissylvesterhurd/52852294515/<->https://live.staticflickr.com/65535/52852294515_b00fb1e17d_c.jpg'
,'003<->Labradoodle<->https://www.flickr.com/photos/195591092@N07/52064576115/<->https://live.staticflickr.com/65535/52064576115_636e0487bf_c.jpg'
,'004<->Day with Monty 1<->https://www.flickr.com/photos/ianlivesey/49847245082/<->https://live.staticflickr.com/65535/49847245082_41ca62b972_c.jpg'
);


$counter  = 0;

//Bluesky接続 (Json Web Token (JWT) 取得)
$handle   = "**********************";		//あなたのBluesky ID (username.bsky.social など)
$password = "**********************";		//あなたのBluesky パスワード

$jwt = NULL;
$connect_FLG = false;
$logfile = "text_test.log";
$log_FLG = false;

//Main処理
//各行毎で処理する
foreach($readdata as $lines) {
	$items = explode("<->", $lines);// 0=番号, 1=タイトル, 2=URL, 3=imageURL
	//画像URLの無いものは破棄
	if(!(isset($items[3])))	continue;

	if(!$connect_FLG){
		$connect_FLG = true;
		// Json Web Token (JWT) 取得
		$jwt = login($handle, $password);
	}

	//-----------------------------------------------------------------------------------------------
	//Blusky 投稿
	$title= (string)$items[1];
	$link = (string)$items[2];
	$image= (string)$items[3];
	$text = $title . " #Flickr #PublicDomain \n" . $link;

	$facets = [];
	//この辺の facets でのタグ付けやLink付けを丸投げでやってくれるライブラリーもあるらしい
	//Linkを付ける
    $linkStart = strpos($text, $link);
    $linkEnd   = $linkStart + strlen($link);
    $facets[] = [
            'index' => [
                'byteStart' => $linkStart,
                'byteEnd' => $linkEnd
            ],
            'features' => [
                [
                    '$type' => 'app.bsky.richtext.facet#link',
                    'uri' => $link
                ]
            ]
    ];
	//HashTagを付ける
	$tagstring = "#Flickr";// # を含む
    $linkStart = strpos($text, $tagstring);
    $linkEnd   = $linkStart + strlen($tagstring);
    $facets[] = [
            'index' => [
                'byteStart' => $linkStart,
                'byteEnd' => $linkEnd
            ],
            'features' => [
                [
                    '$type' => 'app.bsky.richtext.facet#tag',
                    'tag' => "Flickr"	// # を含まない
                ]
            ]
    ];
	$tagstring = "#PublicDomain";// # を含む
    $linkStart = strpos($text, $tagstring);
    $linkEnd   = $linkStart + strlen($tagstring);
    $facets[] = [
            'index' => [
                'byteStart' => $linkStart,
                'byteEnd' => $linkEnd
            ],
            'features' => [
                [
                    '$type' => 'app.bsky.richtext.facet#tag',
                    'tag' => "PublicDomain"	// # を含まない
                ]
            ]
    ];
	$record = [
        '$type' => "app.bsky.feed.post",
        'text' => $text,
        'createdAt' => (new DateTime())->format("c"),
		'facets' => $facets,
	];

	//画像データ取得とUpload
	$imageUri = uploadImage($jwt, $image);

    $record['embed'] = [
	    '$type' => 'app.bsky.embed.external',
	    'external'=> [
	    	'uri' => $link,  //リンクカードのURL
	      	'title' => $title,
	      	'description' => 'Blog記事用のネタ投稿です。descriptionは省略可。titleは省略するとurlになるよ',
	      	'thumb' => $imageUri,
		],
    ];

	$response = post_w_link($handle, $jwt, $record);

	file_put_contents($logfile,print_r($response,true),$log_FLG);
	if(!$log_FLG) $log_FLG = FILE_APPEND;

	if(isset($response['validationStatus'])){
		if($response['validationStatus'] == 'valid'){
			//投稿成功
			$counter++;
		}
	}
	//-----------------------------------------------------------------------------------------------

	usleep(100000 * 3);//0.1秒 x N停止 (sleep 1秒 = usleep 1000000)
}


echo "total : " . (string)$counter ." items post\n";



/////////////////////////////////////////////////////////////////

function login($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_FOLLOWLOCATION => true,
		CURLOPT_MAXREDIRS => 1000,
		CURLOPT_COOKIEJAR  => dirname(__FILE__) . '/cookie_bsky.txt',
		CURLOPT_COOKIEFILE => dirname(__FILE__) . '/cookie_bsky.txt',
        CURLOPT_POST => true,
		CURLOPT_USERAGENT  => $GLOBALS['UAstring'],
        CURLOPT_HTTPHEADER => [
            "Content-Type: application/json",
        ],
        CURLOPT_POSTFIELDS => json_encode([
            "identifier" => $handle,
            "password" => $password,
        ]),
    ]);
    $response = curl_exec($ch);
	if(curl_error($ch)){
		echo "login error : " . curl_error($ch) . "\n";
		die();
	}
    curl_close($ch);
    $responseJson = json_decode($response, true);
	if(isset($responseJson["accessJwt"])){
	    return $responseJson["accessJwt"];
	}else{
		print_r($responseJson);
	}
}

function post_w_link($handle, $jwt, $record)
{
    $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_FOLLOWLOCATION => true,
		CURLOPT_MAXREDIRS => 1000,
		CURLOPT_COOKIEJAR  => dirname(__FILE__) . '/cookie_bsky.txt',
		CURLOPT_COOKIEFILE => dirname(__FILE__) . '/cookie_bsky.txt',
        CURLOPT_POST => true,
		CURLOPT_USERAGENT  => $GLOBALS['UAstring'],
        CURLOPT_HTTPHEADER => [
            "Content-Type: application/json",
            "Authorization: Bearer {$jwt}",
        ],
        CURLOPT_POSTFIELDS => json_encode([
            "repo" => $handle,
            "collection" => "app.bsky.feed.post",
            "record" => $record,
        ]),
    ]);
    $response = curl_exec($ch);
	if(curl_error($ch)){
		echo "post error : " . curl_error($ch) . "\n";
		die();
	}
    curl_close($ch);
    $responseJson = json_decode($response, true);
    return $responseJson;
}

function uploadImage($jwt, $imagePath)
{
    $imageData = file_get_contents($imagePath);
//  $mime = mime_content_type($imagePath);
    //Flickr は形式固定なのでローカル保存しない
	//他サイト等て混在する場合は一時的にローカル環境に出力してmime_content_typeで判別
    $mime = 'image/jpeg';

    $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_MAXREDIRS => 1000,
		CURLOPT_COOKIEJAR  => dirname(__FILE__) . '/cookie_bsky.txt',
		CURLOPT_COOKIEFILE => dirname(__FILE__) . '/cookie_bsky.txt',
        CURLOPT_POST => true,
		CURLOPT_USERAGENT  => $GLOBALS['UAstring'],
        CURLOPT_HTTPHEADER => [
            "Content-Type: $mime",
            "Authorization: Bearer {$jwt}",
        ],
        CURLOPT_POSTFIELDS => $imageData,
    ]);

    $response = curl_exec($ch);
	if(curl_error($ch)){
		echo "upload error : " . curl_error($ch) . "\n";
		die();
	}
    curl_close($ch);
    $responseJson = json_decode($response, true);
    return $responseJson['blob'] ?? null;	//PHP 7.x 以降要
	/*
		3項演算の亜種
		$x ?? $y; は
		isset($x) ? $x : $y; と同じ効果です(PHP 7.x以上)
	*/
}
?>