friendship ended with social-app. php is my new best friend
1<?php 2error_reporting(E_ALL); 3ini_set('display_errors', 'On'); 4ini_set('log_errors_max_len', '0'); 5 6require_once('vendor/autoload.php'); 7require_once('config.php'); 8require_once('lib/bskyToucher.php'); 9require_once('lib/bskyProvider.php'); 10 11use flight\Engine; 12use League\CommonMark\CommonMarkConverter; 13use React\Promise\Deferred; 14use React\EventLoop\Loop; 15use React\Promise\Promise; 16use GuzzleHttp\Client; 17use Tracy\Debugger; 18use Tracy\OutputDebugger; 19use Matrix\Async; 20use GuzzleHttp\Psr7\HttpFactory; 21use chillerlan\OAuth\OAuthOptions; 22use Smallnest\Bsky\BskyProvider; 23 24$bskyToucher = new BskyToucher(); 25 26$favoriteFeeds = array_map(function ($feed) use ($bskyToucher) { 27 return $bskyToucher->getFeedInfo($feed); 28}, FAVORITE_FEEDS); 29 30function getPostOgImage(object $post): ?string { 31 if (!property_exists($post, 'embedType') || !$post->embedType) return null; 32 33 if ($post->embedType === 'app.bsky.embed.images') { 34 return $post->embeds[0]->imgUrl; 35 } else if ($post->embedType === 'app.bsky.embed.external' || $post->embedType === 'app.bsky.embed.video') { 36 return $post->embeds[0]->thumb; 37 } else if ($post->embedType === 'app.bsky.embed.record') { 38 return getPostOgImage($post->embeds[0]->post); 39 } 40 return null; 41} 42 43/*Debugger::enable(); 44// This where errors and exceptions will be logged. Make sure this directory exists and is writable. 45Debugger::$logDirectory = __DIR__ . '/../log/'; 46//Debugger::$strictMode = true; // display all errors 47// Debugger::$strictMode = E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED; // all errors except deprecated notices 48if (Debugger::$showBar) { 49 // This is specific to the Tracy Extension for Flight if you've included that 50 // otherwise comment this out. 51 //new TracyExtensionLoader($app); 52}*/ 53 54Flight::set('frontpageFeed', FRONTPAGE_FEED); 55Flight::set('slingshotInstance', SLINGSHOT_INSTANCE); 56Flight::set('constellationInstance', CONSTELLATION_INSTANCE); 57Flight::set('plcDirectory', PLC_DIRECTORY); 58Flight::set('defaultPds', DEFAULT_PDS); 59Flight::set('publicApi', PUBLIC_API); 60Flight::set('frontpageFeed', FRONTPAGE_FEED); 61Flight::set('defaultRelay', DEFAULT_RELAY); 62Flight::set('userAuth', null); 63Flight::set('flight.log_errors', false); 64Flight::set('flight.handle_errors', false); 65Flight::set('flight.content_length', false); 66 67Flight::set('standardParams', [ 68 'siteTitle' => SITE_TITLE, 69 'themes' => THEMES, 70 'fonts' => FONTS, 71 'setTheme' => array_key_exists('sbs_theme', $_COOKIE) ? $_COOKIE['sbs_theme'] : DEFAULT_THEME, 72 'setFont' => array_key_exists('sbs_font', $_COOKIE) ? $_COOKIE['sbs_font'] : DEFAULT_FONT, 73 'userAuth' => Flight::get('userAuth'), 74 'favFeeds' => $favoriteFeeds, 75 'pages' => PAGES, 76 'links' => LINKS, 77 'ogdomain' => 'https://'.SITE_DOMAIN 78]); 79 80Flight::route('/', function () { 81 $bskyToucher = new BskyToucher(); 82 $posts = $bskyToucher->getFeed(Flight::get('frontpageFeed')); 83 $feedInfo = $bskyToucher->getFeedInfo(Flight::get('frontpageFeed')); 84 $latte = new Latte\Engine; 85 $latte->render('./templates/home.latte', array_merge(Flight::get('standardParams'), [ 86 'mainClass' => 'home feed', 87 'feedInfo' => $feedInfo, 88 'posts' => array_map(function ($p) { return $p->post; }, $posts->feed), 89 'cursor' => $posts->cursor, 90 'feedAtUri' => FRONTPAGE_FEED, 91 'ogtitle' => SITE_TITLE, 92 'ogdesc' => SITE_DESC, 93 'ogimage' => '', 94 'ogurl' => 'https://'.SITE_DOMAIN.'/' 95 ])); 96}); 97 98Flight::route('/u/@handle:[a-z0-9\.]+/@rkey:[a-z0-9]+', function (string $handle, string $rkey): void { 99 $bskyToucher = new BskyToucher(); 100 $did = $bskyToucher->resolveHandle($handle); 101 $userInfo = $bskyToucher->getUserInfo($handle); 102 $atUri = 'at://'.$did.'/app.bsky.feed.post/'.$rkey; 103 $latte = new Latte\Engine; 104 $latte->render('./templates/single.latte', array_merge(Flight::get('standardParams'), [ 105 'mainClass' => 'post', 106 'post' => $atUri, 107 'displayName' => $userInfo->displayName, 108 'handle' => $handle, 109 'ogtitle' => SITE_TITLE." | ".$userInfo->displayName." (@".$handle.")", 110 'ogdesc' => '', //$post->content, 111 'ogimage' => '', //getPostOgImage($post), 112 'ogurl' => 'https://'.SITE_DOMAIN.'/u/'.$handle.'/'.$rkey 113 ])); 114}); 115 116Flight::route('/u/@handle:[a-z0-9\.]+(/@tab:[a-z]+)', function (string $handle, ?string $tab): void { 117 $bskyToucher = new BskyToucher(); 118 $user = $bskyToucher->getUserInfo($handle, 'handle'); 119 $posts = $bskyToucher->getUserPosts($user->did); 120 $latte = new Latte\Engine; 121 $latte->render('./templates/profile.latte', array_merge(Flight::get('standardParams'), [ 122 'mainClass' => 'profile', 123 'handle' => $handle, 124 'posts' => array_map(function ($p) { return $p->uri; }, $posts->records), 125 'cursor' => $posts->cursor, 126 'user' => $user, 127 'ogtitle' => SITE_TITLE." | ".$user->displayName." (@".$user->handle.")", 128 'ogdesc' => $user->description, 129 'ogimage' => $user->avatar, 130 'ogurl' => 'https://'.SITE_DOMAIN.'/u/'.$user->handle.'/' 131 ])); 132}); 133 134Flight::route('/f/@did:did:plc:[0-9a-z]+/@name:[a-z0-9\-\_]+', function (string $did, string $name): void { 135 $bskyToucher = new BskyToucher(); 136 $feedUrl = "at://".$did."/app.bsky.feed.generator/".$name; 137 $feedInfo = $bskyToucher->getFeedInfo($feedUrl); 138 $creatorInfo = $bskyToucher->getUserInfo($feedInfo->creatorDid, 'did'); 139 $posts = $bskyToucher->getFeed($feedUrl); 140 $latte = new Latte\Engine; 141 $latte->render('./templates/feed.latte', array_merge(Flight::get('standardParams'), [ 142 'mainClass' => 'feed', 143 'posts' => array_map(function ($p) { return $p->post; }, $posts->feed), 144 'cursor' => '', 145 'feedName' => $feedInfo->title, 146 'feedAvatar' => $feedInfo->avatar, 147 'feedDescription' => $feedInfo->description, 148 'feedAtUri' => $feedUrl, 149 'feedAuthorName' => $creatorInfo->displayName, 150 'feedAuthorHandle' => $creatorInfo->handle, 151 'feedAuthorDid' => $creatorInfo->did, 152 'feedAuthorPds' => $creatorInfo->pds, 153 'ogtitle' => SITE_TITLE." | ".$feedInfo->title, 154 'ogdesc' => $feedInfo->description, 155 'ogimage' => $feedInfo->avatar, 156 'ogurl' => 'https://'.SITE_DOMAIN.'/f/'.$did.'/'.$name 157 ])); 158}); 159 160Flight::route('/s', function (): void { 161 $latte = new Latte\Engine; 162 $latte->render('./templates/search.latte', array_merge(Flight::get('standardParams'), [ 163 'mainClass' => 'search', 164 'params' => $_GET, 165 'ogtitle' => SITE_TITLE." | search".(array_key_exists('s', $_GET) ? ': '.$_GET['s'] : ''), 166 'ogdesc' => SITE_DESC, 167 'ogimage' => '', 168 'ogurl' => 'https://'.SITE_DOMAIN.'/u/'.$handle.'/'.$rkey 169 ])); 170}); 171 172Flight::route('/login', function(): void { 173 $options = new OAuthOptions([ 174 'key' => 'https://'.SITE_DOMAIN.CLIENT_ID, 175 'secret' => CLIENT_SECRET, 176 'callbackURL' => 'http://127.0.0.1/login', 177 'sessionStart' => true, 178 ]); 179 $connector = new React\Socket\Connector([ 180 'dns' => '1.1.1.1' 181 ]); 182 $http = new React\Http\Browser($connector); 183 $httpFactory = new HttpFactory(); 184 $client = new GuzzleHttp\Client([ 185 'verify' => true, 186 'headers' => [ 187 'User-Agent' => USER_AGENT_STR 188 ] 189 ]); 190 $provider = new BskyProvider($options, $client, $httpFactory, $httpFactory, $httpFactory); 191 $name = $provider->getName(); 192 $username = $_GET['username']; 193 $bskyToucher = new BskyToucher(); 194 $userInfo = $bskyToucher->getUserInfo($username); 195 if (!$userInfo) die(1); 196 $pds = $userInfo->pds; 197 $provider->setPds($pds); 198 $jwt_header = base64_encode(json_encode([ 199 'alg' => 'ES256', 200 'typ' => 'JWT' 201 ])); 202 $jwt_body = base64_encode(json_encode([ 203 'iss' => $userInfo->did, 204 'sub' => 'https://'.SITE_DOMAIN.CLIENT_ID, 205 'aud' => 'did:web:'.str_replace("/", str_replace("https://", $pds)), 206 'jti' => hash('sha512', bin2hex(random_bytes(256 / 2))), 207 'iat' => strtotime('now') 208 ])); 209 $jwt = $jwt_header.$jwt_body.base64_encode(CERT); 210 $client->setDefaultOption('headers', [ 211 'User-Agent' => USER_AGENT_STR, 212 'Authorization' => 'Bearer: '.$jwt 213 ]); 214 if (isset($_GET['login']) && $_GET['login'] === $name) { 215 $auth_url = $provider->getAuthorizationUrl(); 216 header('Location: '.$auth_url); 217 die(1); 218 } else if (isset($_GET['code'], $_GET['state'])) { 219 $token = $provider->getAccessToken($_GET['code'], $_GET['state']); 220 221 // save the token in a permanent storage 222 // [...] 223 224 // access granted, redirect 225 header('Location: ?granted='.$name); 226 die(1); 227 } else if (isset($_GET['granted']) && $_GET['granted'] === $name) { 228 die(1); 229 } else if (isset($_GET['error'])) { 230 die(1); 231 } 232 $latte = new Latte\Engine; 233 $latte->render('./templates/login.latte', array_merge(Flight::get('standardParams'), [ 234 'mainClass' => 'form', 235 'ogtitle' => SITE_TITLE." | login", 236 'ogdesc' => SITE_DESC, 237 'ogimage' => '', 238 'ogurl' => 'https://'.SITE_DOMAIN.'/login' 239 ])); 240}); 241 242// https://shimaenaga.veryroundbird.house/oauth/authorize?client_id=https%3A%2F%2Ftangled.org%2Foauth%2Fclient-metadata.json&request_uri=urn%3Aietf%3Aparams%3Aoauth%3Arequest_uri%3Areq-2399ff42af66498132ebf8de809254b7 243 244Flight::route('/createaccount', function(): void { 245 $latte = new Latte\Engine; 246 $latte->render('./templates/create.latte', array_merge(Flight::get('standardParams'), [ 247 'mainClass' => 'form', 248 'ogtitle' => SITE_TITLE." | create account", 249 'ogdesc' => SITE_DESC, 250 'ogimage' => '', 251 'ogurl' => SITE_DOMAIN.'/createaccount' 252 ])); 253}); 254 255Flight::route('/api/post/@did/@rkey', function (string $did, string $rkey): void { 256 $bskyToucher = new BskyToucher(); 257 $ret = $bskyToucher->getPost($did, $rkey, true); 258 if ($ret) { 259 $latte = new Latte\Engine; 260 $latte->render('./templates/_partials/post.latte', array_merge(Flight::get('standardParams'), [ 261 'post' => $ret 262 ])); 263 die(1); 264 } 265 Flight::json(['error' => 'malformed response or bad vibes']); 266}); 267 268Flight::route('/api/likes/@did/@rkey', function (string $did, string $rkey): void { 269 $bskyToucher = new BskyToucher(); 270 $ret = $bskyToucher->getLikes('at://'.$did.'/app.bsky.feed.post/'.$rkey); 271 if ($ret) { 272 $latte = new Latte\Engine; 273 $output = $latte->renderToString('./templates/_partials/interaction_list.latte', array_merge(Flight::get('standardParams'), [ 274 'interactions' => $ret->records 275 ])); 276 $ret->rendered = $output; 277 Flight::json($ret); 278 } 279}); 280 281Flight::route('/api/reposts/@did/@rkey', function (string $did, string $rkey): void { 282 $bskyToucher = new BskyToucher(); 283 $ret = $bskyToucher->getReposts('at://'.$did.'/app.bsky.feed.post/'.$rkey); 284 if ($ret) { 285 $latte = new Latte\Engine; 286 $output = $latte->renderToString('./templates/_partials/interaction_list.latte', array_merge(Flight::get('standardParams'), [ 287 'interactions' => $ret->records 288 ])); 289 $ret->rendered = $output; 290 Flight::json($ret); 291 } 292}); 293 294Flight::route('/api/replies/@did/@rkey', function (string $did, string $rkey): void { 295 $bskyToucher = new BskyToucher(); 296 $ret = $bskyToucher->getReplies('at://'.$did.'/app.bsky.feed.post/'.$rkey); 297 if ($ret) { 298 $latte = new Latte\Engine; 299 $output = $latte->renderToString('./templates/_partials/feedPosts.latte', array_merge(Flight::get('standardParams'), [ 300 'total' => $ret->total, 301 'cursor' => $ret->cursor, 302 'posts' => array_map(function($p) { return $p->uri; }, $ret->records) 303 ])); 304 $ret->rendered = $output; 305 Flight::json($ret); 306 } 307}); 308 309Flight::route('/api/quotes/@did/@rkey', function (string $did, string $rkey): void { 310 $bskyToucher = new BskyToucher(); 311 $ret = $bskyToucher->getLikes('at://'.$did.'/app.bsky.feed.post/'.$rkey); 312 if ($ret) { 313 $latte = new Latte\Engine; 314 $output = $latte->renderToString('./templates/_partials/interaction_list.latte', array_merge(Flight::get('standardParams'), [ 315 'interactions' => $ret->records 316 ])); 317 $ret->rendered = $output; 318 Flight::json($ret); 319 } 320}); 321 322Flight::route('/api/user/@handle', function (string $handle): void { 323 $bskyToucher = new BskyToucher(); 324 $ret = $bskyToucher->getUserInfo($handle); 325 if ($ret) { 326 Flight::json($ret); 327 die(1); 328 } 329 Flight::json(['error' => 'malformed response or bad vibes']); 330}); 331 332Flight::route('/api/feed/@did/@rkey', function (string $did, string $rkey): void { 333 $bskyToucher = new BskyToucher(); 334}); 335 336Flight::route('/@page', function (string $page): void { 337 $latte = new Latte\Engine; 338 $converter = new CommonMarkConverter(); 339 $md = $converter->convert(file_get_contents('./pages/'.$page.'.md')); 340 $latte->render('./templates/page.latte', array_merge(Flight::get('standardParams'), [ 341 'mainClass' => 'page', 342 'content' => $md, 343 'ogtitle' => SITE_TITLE." | ".$page, 344 'ogdesc' => SITE_DESC, 345 'ogimage' => '', 346 'ogurl' => SITE_DOMAIN.'/'.$page 347 ])); 348}); 349 350Flight::start(); 351 352?>