getFeedInfo($feed); }, FAVORITE_FEEDS); function getPostOgImage(object $post): ?string { if (!property_exists($post, 'embedType') || !$post->embedType) return null; if ($post->embedType === 'app.bsky.embed.images') { return $post->embeds[0]->imgUrl; } else if ($post->embedType === 'app.bsky.embed.external' || $post->embedType === 'app.bsky.embed.video') { return $post->embeds[0]->thumb; } else if ($post->embedType === 'app.bsky.embed.record') { return getPostOgImage($post->embeds[0]->post); } return null; } /*Debugger::enable(); // This where errors and exceptions will be logged. Make sure this directory exists and is writable. Debugger::$logDirectory = __DIR__ . '/../log/'; //Debugger::$strictMode = true; // display all errors // Debugger::$strictMode = E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED; // all errors except deprecated notices if (Debugger::$showBar) { // This is specific to the Tracy Extension for Flight if you've included that // otherwise comment this out. //new TracyExtensionLoader($app); }*/ Flight::set('frontpageFeed', FRONTPAGE_FEED); Flight::set('slingshotInstance', SLINGSHOT_INSTANCE); Flight::set('constellationInstance', CONSTELLATION_INSTANCE); Flight::set('plcDirectory', PLC_DIRECTORY); Flight::set('defaultPds', DEFAULT_PDS); Flight::set('publicApi', PUBLIC_API); Flight::set('frontpageFeed', FRONTPAGE_FEED); Flight::set('defaultRelay', DEFAULT_RELAY); Flight::set('userAuth', array_key_exists('sbs_'.SITE_DOMAIN, $_SESSION) ? $_SESSION['sbs_'.SITE_DOMAIN] : null); Flight::set('userPds', array_key_exists('sbs_'.SITE_DOMAIN.'_pds', $_SESSION) ? $_SESSION['sbs_'.SITE_DOMAIN.'_pds'] : null); Flight::set('userInfo', array_key_exists('sbs_'.SITE_DOMAIN.'_userinfo', $_SESSION) ? $_SESSION['sbs_'.SITE_DOMAIN.'_userinfo'] : null); Flight::set('flight.log_errors', false); Flight::set('flight.handle_errors', false); Flight::set('flight.content_length', false); Flight::set('standardParams', [ 'siteTitle' => SITE_TITLE, 'themes' => THEMES, 'fonts' => FONTS, 'setTheme' => array_key_exists('sbs_theme', $_COOKIE) ? $_COOKIE['sbs_theme'] : DEFAULT_THEME, 'setFont' => array_key_exists('sbs_font', $_COOKIE) ? $_COOKIE['sbs_font'] : DEFAULT_FONT, 'userAuth' => Flight::get('userAuth'), 'userPds' => Flight::get('userPds'), 'userInfo' => Flight::Get('userInfo'), 'favFeeds' => $favoriteFeeds, 'pages' => PAGES, 'links' => LINKS, 'ogdomain' => 'https://'.SITE_DOMAIN ]); Flight::route('/', function () { $bskyToucher = new BskyToucher(); $posts = $bskyToucher->getFeed(Flight::get('frontpageFeed')); $feedInfo = $bskyToucher->getFeedInfo(Flight::get('frontpageFeed')); $latte = new Latte\Engine; $latte->render('./templates/home.latte', array_merge(Flight::get('standardParams'), [ 'mainClass' => 'home feed', 'feedInfo' => $feedInfo, 'posts' => array_map(function ($p) { return $p->post; }, $posts->feed), 'cursor' => $posts->cursor, 'feedAtUri' => FRONTPAGE_FEED, 'ogtitle' => SITE_TITLE, 'ogdesc' => SITE_DESC, 'ogimage' => '', 'ogurl' => 'https://'.SITE_DOMAIN.'/' ])); }); Flight::route('/u/@handle:[a-z0-9\.]+/@rkey:[a-z0-9]+', function (string $handle, string $rkey): void { $bskyToucher = new BskyToucher(); $did = $bskyToucher->resolveHandle($handle); $userInfo = $bskyToucher->getUserInfo($handle); $atUri = 'at://'.$did.'/app.bsky.feed.post/'.$rkey; $latte = new Latte\Engine; $latte->render('./templates/single.latte', array_merge(Flight::get('standardParams'), [ 'mainClass' => 'post', 'post' => $atUri, 'displayName' => $userInfo->displayName, 'handle' => $handle, 'ogtitle' => SITE_TITLE." | ".$userInfo->displayName." (@".$handle.")", 'ogdesc' => '', //$post->content, 'ogimage' => '', //getPostOgImage($post), 'ogurl' => 'https://'.SITE_DOMAIN.'/u/'.$handle.'/'.$rkey ])); }); Flight::route('/u/@handle:[a-z0-9\.]+(/@tab:[a-z]+)', function (string $handle, ?string $tab): void { $bskyToucher = new BskyToucher(); $user = $bskyToucher->getUserInfo($handle, 'handle'); $posts = $bskyToucher->getUserPosts($user->did); $latte = new Latte\Engine; $latte->render('./templates/profile.latte', array_merge(Flight::get('standardParams'), [ 'mainClass' => 'profile', 'handle' => $handle, 'posts' => array_map(function ($p) { return $p->uri; }, $posts->records), 'cursor' => $posts->cursor, 'user' => $user, 'ogtitle' => SITE_TITLE." | ".$user->displayName." (@".$user->handle.")", 'ogdesc' => $user->description, 'ogimage' => $user->avatar, 'ogurl' => 'https://'.SITE_DOMAIN.'/u/'.$user->handle.'/' ])); }); Flight::route('/f/@did:did:plc:[0-9a-z]+/@name:[a-z0-9\-\_]+', function (string $did, string $name): void { $bskyToucher = new BskyToucher(); $feedUrl = "at://".$did."/app.bsky.feed.generator/".$name; $feedInfo = $bskyToucher->getFeedInfo($feedUrl); $creatorInfo = $bskyToucher->getUserInfo($feedInfo->creatorDid, 'did'); $posts = $bskyToucher->getFeed($feedUrl); $latte = new Latte\Engine; $latte->render('./templates/feed.latte', array_merge(Flight::get('standardParams'), [ 'mainClass' => 'feed', 'posts' => array_map(function ($p) { return $p->post; }, $posts->feed), 'cursor' => '', 'feedName' => $feedInfo->title, 'feedAvatar' => $feedInfo->avatar, 'feedDescription' => $feedInfo->description, 'feedAtUri' => $feedUrl, 'feedAuthorName' => $creatorInfo->displayName, 'feedAuthorHandle' => $creatorInfo->handle, 'feedAuthorDid' => $creatorInfo->did, 'feedAuthorPds' => $creatorInfo->pds, 'ogtitle' => SITE_TITLE." | ".$feedInfo->title, 'ogdesc' => $feedInfo->description, 'ogimage' => $feedInfo->avatar, 'ogurl' => 'https://'.SITE_DOMAIN.'/f/'.$did.'/'.$name ])); }); Flight::route('/s', function (): void { $latte = new Latte\Engine; $latte->render('./templates/search.latte', array_merge(Flight::get('standardParams'), [ 'mainClass' => 'search', 'params' => $_GET, 'ogtitle' => SITE_TITLE." | search".(array_key_exists('s', $_GET) ? ': '.$_GET['s'] : ''), 'ogdesc' => SITE_DESC, 'ogimage' => '', 'ogurl' => 'https://'.SITE_DOMAIN.'/u/'.$handle.'/'.$rkey ])); }); Flight::route('/login', function(): void { if (isset($_GET['username'])) { $username = $_GET['username']; $bskyToucher = new BskyToucher(); $userInfo = $bskyToucher->getUserInfo($username); if (!$userInfo) die(1); $pds = $userInfo->pds; $options = new OAuthOptions([ 'key' => 'https://'.SITE_DOMAIN.CLIENT_ID, 'secret' => CLIENT_SECRET, 'callbackURL' => 'https://'.SITE_DOMAIN.'/login', 'sessionStart' => true, 'sessionStorageVar' => 'sbs_'.SITE_DOMAIN ]); $storage = new SessionStorage($options); $connector = new React\Socket\Connector([ 'dns' => '1.1.1.1' ]); $http = new React\Http\Browser($connector); $httpFactory = new HttpFactory(); $token_builder = Builder::new(new JoseEncoder(), ChainedFormatter::default()); $algorithm = new Sha256(); $signing_key = InMemory::file(CERT_PATH); $now = new DateTimeImmutable(); $token = $token_builder ->withHeader('alg', 'ES256') ->withHeader('typ', 'JWT') ->withHeader('kid', 'ocwgKj_O7H9at1sL6yWf9ZZ82NOM7D0xlN8HGIyWH6M') ->issuedBy('https://'.SITE_DOMAIN.CLIENT_ID) ->identifiedBy(uniqid()) ->relatedTo('https://'.SITE_DOMAIN.CLIENT_ID) ->permittedFor($pds) ->issuedAt($now->modify('-5 seconds')) ->getToken($algorithm, $signing_key); $client = new GuzzleHttp\Client([ 'verify' => true, 'headers' => [ 'User-Agent' => USER_AGENT_STR, 'Authorization' => 'Bearer: '.$token->toString() ] ]); $provider = new BskyProvider($options, $client, $httpFactory, $httpFactory, $httpFactory); $provider->setPds($pds); $name = $provider->getName(); if (isset($_GET['login']) && $_GET['login'] === $name) { $auth_url = $provider->getAuthorizationUrl([ 'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer', 'client_assertion' => $token->toString() ]); Flight::redirect($auth_url); die(1); } else if (isset($_GET['code'], $_GET['iss'])) { $storage->storeAccessToken($_GET['code'], $name); $_SESSION['sbs_'.SITE_DOMAIN.'_pds'] = $_GET['iss']; $_SESSION['sbs_'.SITE_DOMAIN.'_userinfo'] = $bskyToucher->getUserInfo(); Flight::redirect('/'); die(1); } else if (isset($_GET['error'])) { die(1); } } else { $latte = new Latte\Engine; $latte->render('./templates/login.latte', array_merge(Flight::get('standardParams'), [ 'mainClass' => 'form', 'ogtitle' => SITE_TITLE." | login", 'ogdesc' => SITE_DESC, 'ogimage' => '', 'ogurl' => 'https://'.SITE_DOMAIN.'/login' ])); } }); Flight::route('/logout', function(): void { unset($_SESSION['sbs_'.SITE_DOMAIN]); unset($_SESSION['sbs_'.SITE_DOMAIN.'_pds']); unset($_SESSION['sbs_'.SITE_DOMAIN.'_userinfo']); Flight::redirect('/'); }); Flight::route('/createaccount', function(): void { $latte = new Latte\Engine; $latte->render('./templates/create.latte', array_merge(Flight::get('standardParams'), [ 'mainClass' => 'form', 'ogtitle' => SITE_TITLE." | create account", 'ogdesc' => SITE_DESC, 'ogimage' => '', 'ogurl' => SITE_DOMAIN.'/createaccount' ])); }); Flight::route('/api/post/@did/@rkey', function (string $did, string $rkey): void { $bskyToucher = new BskyToucher(); $ret = $bskyToucher->getPost($did, $rkey, true); if ($ret) { $latte = new Latte\Engine; $latte->render('./templates/_partials/post.latte', array_merge(Flight::get('standardParams'), [ 'post' => $ret ])); die(1); } Flight::json(['error' => 'malformed response or bad vibes']); }); Flight::route('/api/likes/@did/@rkey', function (string $did, string $rkey): void { $bskyToucher = new BskyToucher(); $ret = $bskyToucher->getLikes('at://'.$did.'/app.bsky.feed.post/'.$rkey); if ($ret) { $latte = new Latte\Engine; $output = $latte->renderToString('./templates/_partials/interaction_list.latte', array_merge(Flight::get('standardParams'), [ 'interactions' => $ret->records ])); $ret->rendered = $output; Flight::json($ret); } }); Flight::route('/api/reposts/@did/@rkey', function (string $did, string $rkey): void { $bskyToucher = new BskyToucher(); $ret = $bskyToucher->getReposts('at://'.$did.'/app.bsky.feed.post/'.$rkey); if ($ret) { $latte = new Latte\Engine; $output = $latte->renderToString('./templates/_partials/interaction_list.latte', array_merge(Flight::get('standardParams'), [ 'interactions' => $ret->records ])); $ret->rendered = $output; Flight::json($ret); } }); Flight::route('/api/replies/@did/@rkey', function (string $did, string $rkey): void { $bskyToucher = new BskyToucher(); $ret = $bskyToucher->getReplies('at://'.$did.'/app.bsky.feed.post/'.$rkey); if ($ret) { $latte = new Latte\Engine; $output = $latte->renderToString('./templates/_partials/feedPosts.latte', array_merge(Flight::get('standardParams'), [ 'total' => $ret->total, 'cursor' => $ret->cursor, 'posts' => array_map(function($p) { return $p->uri; }, $ret->records) ])); $ret->rendered = $output; Flight::json($ret); } }); Flight::route('/api/quotes/@did/@rkey', function (string $did, string $rkey): void { $bskyToucher = new BskyToucher(); $ret = $bskyToucher->getLikes('at://'.$did.'/app.bsky.feed.post/'.$rkey); if ($ret) { $latte = new Latte\Engine; $output = $latte->renderToString('./templates/_partials/interaction_list.latte', array_merge(Flight::get('standardParams'), [ 'interactions' => $ret->records ])); $ret->rendered = $output; Flight::json($ret); } }); Flight::route('/api/user/@handle', function (string $handle): void { $bskyToucher = new BskyToucher(); $ret = $bskyToucher->getUserInfo($handle); if ($ret) { Flight::json($ret); die(1); } Flight::json(['error' => 'malformed response or bad vibes']); }); Flight::route('/api/feed/@did/@rkey', function (string $did, string $rkey): void { $bskyToucher = new BskyToucher(); }); Flight::route('/@page', function (string $page): void { $latte = new Latte\Engine; $converter = new CommonMarkConverter(); $md = $converter->convert(file_get_contents('./pages/'.$page.'.md')); $latte->render('./templates/page.latte', array_merge(Flight::get('standardParams'), [ 'mainClass' => 'page', 'content' => $md, 'ogtitle' => SITE_TITLE." | ".$page, 'ogdesc' => SITE_DESC, 'ogimage' => '', 'ogurl' => SITE_DOMAIN.'/'.$page ])); }); Flight::start(); ?>