friendship ended with social-app. php is my new best friend

add example env file

+5
.env.example
···
+
AUTH_ACCOUNT=
+
AUTH_KEYTYPE=
+
AUTH_PUBKEY=
+
AUTH_PRIVKEY_HEX=
+
AUTH_PRIVKEY_MULTI=
+7
README.md
···
maybe! i'm not sure if i can or will go for 100% feature parity but i'm going to try my darndest
+
## thanks to
+
+
* [constellation](https://constellation.microcosm.blue) and [slingshot](https://slingshot.microcosm.blue) for a lot of data fetching to reduce load on PDS + dependence on bsky.app
+
* [plc.directory](https://plc.directory) for plc lookups
+
* [pdsls.dev](https://pdsls.dev) for helping me figure out what API endpoints to call from the network tab
+
* [red dwarf](https://reddwarf.whey.party) for being inspirational
+
## suppose i am someone who wanted to install this
ok wowee!! just so you know this isn't fully cooked yet so there's a lot of features missing. there's a lot of stuff that goes into one of these things as it turns out. but if you really want to:
+2
config.php
···
define('DEFAULT_PDS', "https://bsky.social");
define('SLINGSHOT_INSTANCE', "https://slingshot.microcosm.blue");
define('CONSTELLATION_INSTANCE', "https://constellation.microcosm.blue");
+
define('PLC_DIRECTORY', "https://plc.directory");
define('PRETTY_URLS', false);
define('FRONTPAGE_FEED', "at://did:plc:ffkgesg3jsv2j7aagkzrtcvt/app.bsky.feed.generator/aaagllxbcbsje");
define('FAVORITE_FEEDS', [ // list of feeds you want to display in the sidebar to users not logged in
]);
define('DEFAULT_THEME', "catppuccin");
+
define('DEFAULT_FONT', "recursive");
define('SSL_CERT_PATH', '');
?>
+46
css/_partials/_fonts.scss
···
+
@font-face {
+
font-family: 'Alegreya';
+
font-weight: normal;
+
font-style: normal;
+
src: url(@fonts/alegreyasc-regular-webfont.woff2) format('woff2');
+
}
+
+
@font-face {
+
font-family: 'Alegreya';
+
font-weight: bold;
+
font-style: normal;
+
src: url(@fonts/alegreyasc-bold-webfont.woff2) format('woff2');
+
}
+
+
@font-face {
+
font-family: 'Alegreya';
+
font-weight: normal;
+
font-style: italic;
+
src: url(@fonts/alegreyasc-italic-webfont.woff2) format('woff2');
+
}
+
+
@font-face {
+
font-family: 'Alegreya';
+
font-weight: bold;
+
font-style: italic;
+
src: url(@fonts/alegreyasc-bolditalic-webfont.woff2) format('woff2');
+
}
+
+
@font-face {
+
font-family: 'Geometria';
+
font-weight: normal;
+
font-style: normal;
+
src: url(@fonts/geometria-webfont.woff2) format('woff2');
+
}
+
+
[data-font="recursive"] {
+
font-family: 'Recursive Mono', monospace;
+
}
+
+
[data-font="geometria"] {
+
font-family: 'Geometria', sans-serif;
+
}
+
+
[data-font="alegreya"] {
+
font-family: 'Alegreya', serif;
+
}
+31
css/_partials/_layout.scss
···
}
}
}
+
}
+
+
#fonts {
+
margin-bottom: 1em;
+
+
ul {
+
list-style: none;
+
margin: 0;
+
padding: 0;
+
text-align: center;
+
display: flex;
+
justify-content: center;
+
align-items: center;
+
gap: 5px;
+
+
li {
+
a {
+
display: flex;
+
text-indent: -500px;
+
overflow: hidden;
+
color: inherit;
+
text-decoration: none;
+
font-size: 1.5em;
+
+
&:before {
+
content: "Aa";
+
text-indent: 0px;
+
}
+
}
+
}
+
}
}
+1
css/main.scss
···
@use 'themes/rosepine';
@use 'themes/solarized';
+
@use '_partials/_fonts';
@use '_partials/_globals';
@use '_partials/_layout';
@use '_partials/_post';
+2 -2
css/themes/blank.scss
···
-
[data-theme="blank"] {
+
a[data-theme="blank"] {
background-color: #FFFFFF;
@media (prefers-color-scheme: dark) {
···
}
}
-
.blank {
+
body[data-theme="blank"] {
--border-color: #000000;
--background-color: #FFFFFF;
--text-color: #000000;
+2 -2
css/themes/catppuccin.scss
···
-
[data-theme="catppuccin"] {
+
a[data-theme="catppuccin"] {
background-color: #eff1f5;
background-image: linear-gradient(to bottom right, #eff1f5, #1e66f5);
···
}
}
-
.catppuccin {
+
body[data-theme="catppuccin"] {
--border-color: #acb0be;
--background-color: #eff1f5;
--text-color: #4c4f69;
+2 -2
css/themes/dracula.scss
···
-
[data-theme="dracula"] {
+
a[data-theme="dracula"] {
background-color: #faf4ed;
background-image: linear-gradient(to bottom right, #f8f8f2, #ff79c6);
···
}
}
-
.dracula {
+
body[data-theme="dracula"] {
--border-color: #a2a9b1;
--background-color: #f8f8f2;
--text-color: #282a36;
+2 -2
css/themes/rosepine.scss
···
-
[data-theme="rosepine"] {
+
a[data-theme="rosepine"] {
background-color: #faf4ed;
background-image: linear-gradient(to bottom right, #faf4ed, #d7827e);
···
}
}
-
.rosepine {
+
body[data-theme="rosepine"] {
--border-color: #cecacd;
--background-color: #faf4ed;
--text-color: #575279;
+2 -2
css/themes/solarized.scss
···
-
[data-theme="solarized"] {
+
a[data-theme="solarized"] {
background-color: #faf4ed;
background-image: linear-gradient(to bottom right, #faf4ed, #2aa198);
···
}
}
-
.solarized {
+
body[data-theme="solarized"] {
--border-color: #586e75;
--background-color: #fdf6e3;
--text-color: #002b36;
fonts/RecursiveMonoCslSt-BdItalic.woff2

This is a binary file and will not be displayed.

fonts/RecursiveMonoCslSt-Bold.woff2

This is a binary file and will not be displayed.

fonts/RecursiveMonoCslSt-Italic.woff2

This is a binary file and will not be displayed.

fonts/RecursiveMonoCslSt-Regular.woff2

This is a binary file and will not be displayed.

fonts/alegreyasc-bold-webfont.woff

This is a binary file and will not be displayed.

fonts/alegreyasc-bold-webfont.woff2

This is a binary file and will not be displayed.

fonts/alegreyasc-bolditalic-webfont.woff

This is a binary file and will not be displayed.

fonts/alegreyasc-bolditalic-webfont.woff2

This is a binary file and will not be displayed.

fonts/alegreyasc-italic-webfont.woff

This is a binary file and will not be displayed.

fonts/alegreyasc-italic-webfont.woff2

This is a binary file and will not be displayed.

fonts/alegreyasc-regular-webfont.woff

This is a binary file and will not be displayed.

fonts/alegreyasc-regular-webfont.woff2

This is a binary file and will not be displayed.

fonts/geometria-bold-webfont.woff

This is a binary file and will not be displayed.

fonts/geometria-bold-webfont.woff2

This is a binary file and will not be displayed.

fonts/geometria-bolditalic-webfont.woff

This is a binary file and will not be displayed.

fonts/geometria-bolditalic-webfont.woff2

This is a binary file and will not be displayed.

fonts/geometria-italic-webfont.woff

This is a binary file and will not be displayed.

fonts/geometria-italic-webfont.woff2

This is a binary file and will not be displayed.

fonts/geometria-webfont.woff

This is a binary file and will not be displayed.

fonts/geometria-webfont.woff2

This is a binary file and will not be displayed.

+12 -1
index.php
···
<title>smallbird social</title>
<link rel="stylesheet" type="text/css" href="./css/main.scss" />
</head>
-
<body class="<?= Smallnest\Helpers\getTheme() ? Smallnest\Helpers\getTheme() : DEFAULT_THEME; ?>">
+
<body
+
data-theme="<?= Smallnest\Helpers\getTheme() ? Smallnest\Helpers\getTheme() : DEFAULT_THEME; ?>"
+
data-font="<?= Smallnest\Helpers\getFont() ? Smallnest\Helpers\getFont() : DEFAULT_FONT; ?>"
+
>
<div id="page">
<header>
<h1><a href="/"><?php Smallnest\Helpers\renderLogo(); ?>smallbird social</a></h1>
···
<li><a href="#" data-theme="rosepine" title="rosé pine">rosé pine</a></li>
<li><a href="#" data-theme="dracula" title="dracula">dracula</a></li>
<li><a href="#" data-theme="solarized" title="solarized">solarized</a></li>
+
</ul>
+
</div>
+
<div id="fonts">
+
<div id="font-heading">font picker</div>
+
<ul>
+
<li><a href="#" data-font="recursive">recursive mono casual</a></li>
+
<li><a href="#" data-font="geometria">geometria</a></li>
+
<li><a href="#" data-font="alegreya">alegreya</a></li>
</ul>
</div>
<a href="<?= Smallnest\Helpers\getUrl('terms', PRETTY_URLS); ?>">terms</a> &bull; <a href="<?= Smallnest\Helpers\getUrl('privacy', PRETTY_URLS); ?>">privacy</a> &bull; <a href="<?= Smallnest\Helpers\getUrl('about', PRETTY_URLS); ?>">about</a><br /><br />
+10 -3
js/themes.mjs
···
-
document.querySelectorAll('[data-theme]').forEach((elt) => {
+
document.querySelectorAll('a[data-theme]').forEach((elt) => {
elt.addEventListener('click', (e) => {
const theme = e.target.getAttribute('data-theme');
-
document.body.classList = "";
-
document.body.classList.add(theme);
+
document.body.setAttribute("data-theme", theme);
document.cookie = "sbs_theme="+theme+";";
+
});
+
});
+
+
document.querySelectorAll('a[data-font]').forEach((elt) => {
+
elt.addEventListener('click', (e) => {
+
const font = e.target.getAttribute('data-font');
+
document.body.setAttribute("data-font", font);
+
document.cookie = "sbs_font="+font+";";
});
});
+183 -99
lib/bskyToucher.php
···
use Smallnest\Bsky\BskyProvider;
use Smallnest\Helpers;
use GuzzleHttp\Client;
+
use GuzzleHttp\Query;
class BskyToucher {
private $bskyApiBase = 'https://public.api.bsky.app/xrpc/';
···
$httpClient = new Client([
'timeout' => 5.0,
]);
-
$ret = $httpClient->request("GET", $this->slingshotBase.'com.atproto.repo.getRecord?collection='.$collection.'&repo='.$repo.'&rkey='.$rkey);
+
$ret = $httpClient->request("GET", $this->slingshotBase.'com.atproto.repo.getRecord', [
+
'query' => [
+
'collection' => $collection,
+
'repo' => $repo,
+
'rkey' => $rkey
+
]
+
]);
if ($ret && $ret->getReasonPhrase() === 'OK') {
return json_decode($ret->getBody());
···
return false;
}
-
function getConstellationLinkData($target, $collection, $path, $limit = 50, $offset = 0) {
+
function getConstellationLinkData($target, $collection, $path, $limit = 50, $offset = 0, $endpoint = '') {
$httpClient = new Client([
'timeout' => 5.0,
]);
-
$ret = $httpClient->request("GET", $this->constellationBase
-
.'?target='.$target
-
.'&collection='.$collection
-
.'&path='.$path
-
.'&limit='.$limit
-
.'&offset='.$offset);
+
$ret = $httpClient->request("GET", $this->constellationBase.$endpoint, [
+
'query' => [
+
'target' => $target,
+
'collection' => $collection,
+
'path' => $path,
+
'limit' => $limit,
+
'offset' => $offset
+
]
+
]);
if ($ret) {
return json_decode($ret->getBody());
···
"rkey" => "self"
]);
-
$profileData = $this->getPdsData($pds, "app.bsky.actor.getProfile", [
+
/*$profileData = $this->getPdsData($pds, "app.bsky.actor.getProfile", [
'actor' => $did
-
]);
+
]);*/
if ($pdsData) {
return (object) [
···
function renderPost($post, $prettyUrls = false) {
$author = $post->author;
$profileLink = $prettyUrls ? "/profile/".$author->handle : "/?p=profile&handle=".$author->handle;
-
$postLink = $prettyUrls ? $profileLink."/post/".$post->postId : $profileLink."&post=".$post->postId;
-
$likesPlural = "like".($post->likeCount !== 1 ? "s" : "");
-
$repliesPlural = "repl".($post->replyCount !== 1 ? "ies" : "y");
-
$quotesPlural = "quote".($post->quoteCount !== 1 ? "s" : "");
-
$repostsPlural = "repost".($post->repostCount !== 1 ? "s" : "");
+
preg_match('/([a-zA-Z0-9]+)$/', $post->uri, $uriParts);
+
$postId = $uriParts[1];
+
$postLink = $prettyUrls ? $profileLink."/post/".$postId : $profileLink."&post=".$postId;
$postCopyUrl = "https://".SERVER_URL.$postLink;
$embeds = $this->renderEmbeds($post->embedType, $post->embeds, $post);
$imageEmbedClass = $post->embedType === "images" ? " img-".count($post->embeds) : "";
···
<div class="timeAgo"><a href="$postLink">$post->createdAt</a></div>
</div>
<div class="postContent">$post->text</div>
-
<div class="postEmbeds$imageEmbedClass">
-
$embeds
-
</div>
+
$embeds
<div class="postFooter">
<div class="postInteraction">
-
<a href="#">$post->likeCount $likesPlural</a>
-
<a href="$postLink#replies">$post->replyCount $repliesPlural</a>
-
<a href="$postLink#replies">$post->quoteCount $quotesPlural</a>
-
<a href="$postLink#replies">$post->repostCount $repostsPlural</a>
+
<a href="#like">like ($post->likeCount)</a>
+
<a href="#reply">reply ($post->replyCount)</a>
+
<a href="#quote">quote ($post->quoteCount)</a>
+
<a href="#repost">repost ($post->repostCount)</a>
</div>
<div class="postSharing">
<a href="#" data-share="$postCopyUrl">copy link</a>
···
foreach ($embeds as $embed) {
$altSanitized = addslashes(htmlspecialchars($embed->alt));
$ret .= <<<EOD
-
<div class="img" id="img-$context->postId-small">
-
<button type="button" class="btn-invis" popovertarget="img-full-$context->postId"><img src="$embed->thumb" alt="$altSanitized" /></button>
-
<div id="img-full-$context->postId" popover>
-
<img src="$embed->fullsize" alt="$altSanitized" />
-
<div class="alt">$embed->alt</div>
+
<div class="postEmbeds img-$post->embeds">
+
<div class="img" id="img-$context->postId-small">
+
<button type="button" class="btn-invis" popovertarget="img-full-$context->postId"><img src="$embed->thumb" alt="$altSanitized" /></button>
+
<div id="img-full-$context->postId" popover>
+
<img src="$embed->fullsize" alt="$altSanitized" />
+
<div class="alt">$embed->alt</div>
+
</div>
</div>
</div>
EOD;
···
$author = $context->did;
$pds = DEFAULT_PDS;
$ret .= <<<EOD
-
<div class="video">
-
<video
-
src="$pds/xrpc/com.atproto.sync.getBlob?did=$author&cid=$vid"
-
controls
-
></video>
+
<div class="embeds">
+
<div class="video">
+
<video
+
src="$pds/xrpc/com.atproto.sync.getBlob?did=$author&cid=$vid"
+
controls
+
></video>
+
</div>
</div>
EOD;
} else if ($type === 'external') {
···
$thumb = $embeds[0]->thumb;
$desc = $embeds[0]->description;
$ret .= <<<EOD
-
<div class="externalLink">
-
<a href="$uri" rel="external" target="_blank">
-
<div class="externalLinkImage">
-
<img src="$thumb" alt="$title" />
-
</div>
-
<div class="externalLinkInfo">
-
<div class="externalLinkTitle">$title</div>
-
<div class="externalLinkDesc">$desc</div>
-
<div class="externalLinkUri">$uri</div>
-
</div>
-
</a>
+
<div class="embeds">
+
<div class="externalLink">
+
<a href="$uri" rel="external" target="_blank">
+
<div class="externalLinkImage">
+
<img src="$thumb" alt="$title" />
+
</div>
+
<div class="externalLinkInfo">
+
<div class="externalLinkTitle">$title</div>
+
<div class="externalLinkDesc">$desc</div>
+
<div class="externalLinkUri">$uri</div>
+
</div>
+
</a>
+
</div>
</div>
EOD;
+
} else if ($type === 'record') {
+
preg_match('/at:\/\/(did:plc:[a-z0-9]+)\/([a-zA-Z0-9\.])\/([a-z0-9]+)/', $embeds[0], $recComponents);
+
print_r($recComponents);
+
$recDid = $recComponents[1];
+
$recRkey = $recComponents[2];
+
$uri = SERVER_URL.'/?p=post&handle='.$recDid.'&postId='.$recRkey;
+
$rec = $this->getSlingshotData($recDid, 'app.bsky.feed.post', $recRkey);
+
print_r($rec);
+
$ret .= <<<EOD
+
<div class="embeds">
+
<div class="record">
+
<a href="$uri">
+
idk
+
</a>
+
</div>
+
</div>
+
EOD;
}
return $ret;
}
···
$authorInfo = $this->getUserInfo($did);
$ret = $this->getSlingshotData($did, "app.bsky.feed.post", $rkey);
if ($ret) {
-
$uri = $ret->uri;
-
$post = $ret->value;
-
$embeds = [];
-
$embedType = null;
-
$replyTo = null;
-
-
if (property_exists($post, 'reply') && property_exists($post->reply, 'parent')) {
-
preg_match('/^at:\/\/(did:plc:[a-z0-9]+)\/app.bsky.feed.post\/([a-z0-9]+)$/', $post->reply->parent->uri, $atUriSplit);
-
$replyUserInfo = $this->getUserInfo($atUriSplit[1]);
-
$replyTo = $post->reply->parent->uri;/*(object) [
-
'uri' => \Smallnest\Helpers\getUrl(SERVER_URL, PRETTY_URLS, [
-
'p' => $replyUserInfo->handle,
-
'post' => $atUriSplit[2]
-
]),
-
'author' => $this->getUserInfo($atUriSplit[1])
-
];*/
-
}
-
-
if (property_exists($post, 'embed') && property_exists($post->embed, '$type')) {
-
if ($post->embed->{'$type'} === 'app.bsky.embed.images#view') {
-
$embeds = $post->embed->images;
-
$embedType = 'images';
-
} elseif ($post->embed->{'$type'} === 'app.bsky.embed.video#view') {
-
$embeds = [(object) [
-
"video" => $post->embed->cid,
-
"thumb" => $post->embed->thumbnail
-
]];
-
$embedType = 'video';
-
} elseif ($post->embed->{'$type'} === 'app.bsky.embed.external#view') {
-
$embeds = [$post->embed->external];
-
$embedtype = 'external';
-
} elseif ($post->embed->{'$type'} === 'app.bsky.embed.record') {
-
$embedtype = 'record';
-
$embeds = [$post->embed->record->uri];
-
}
-
}
-
-
return (object) [
-
'uri' => $ret->uri,
-
"author" => $authorInfo,
-
"createdAt" => $post->createdAt,
-
"text" => $post->text,
-
'replyCount' => $this->getReplies($uri),
-
'repostCount' => $this->getReposts($uri),
-
'likeCount' => $this->getLikes($uri),
-
'quoteCount' => $this->getQuotes($uri),
-
'embedType' => $embedType,
-
'embeds' => $embeds,
-
'reply' => $replyTo
-
];
+
return $this->tidyPost($ret, $authorInfo);
}
return false;
}
···
return 0;
}
+
function getLikeUsers($post) {
+
$ret = $this->getConstellationLinkData($post, "app.bsky.feed.like", ".subject.uri", "/distinct-dids");
+
if ($ret) {
+
return $ret;
+
}
+
return 0;
+
}
+
function getReposts($post) {
$ret = $this->getConstellationLinkData($post, "app.bsky.feed.repost", ".subject.uri");
if ($ret) {
···
return 0;
}
+
function getRepostUsers($post) {
+
$ret = $this->getConstellationLinkData($post, "app.bsky.feed.repost", ".subject.uri", "/distinct-dids");
+
if ($ret) {
+
return $ret;
+
}
+
return [];
+
}
+
function getQuotes($post) {
$ret = $this->getConstellationLinkData($post, "app.bsky.feed.post", ".embed.record.record.uri");
if ($ret) {
···
return 0;
}
+
function getQuoteRecords($post) {
+
$ret = $this->getConstellationLinkData($post, "app.bsky.feed.post", ".embed.record.record.uri");
+
if ($ret) {
+
return $ret;
+
}
+
return [];
+
}
+
function getReplies($post) {
$ret = $this->getConstellationLinkData($post, "app.bsky.feed.post", ".reply.root.uri");
if ($ret) {
···
return 0;
}
+
function getReplyRecords($post) {
+
$ret = $this->getConstellationLinkData($post, "app.bsky.feed.post", ".reply.root.uri");
+
if ($ret) {
+
return $ret;
+
}
+
return [];
+
}
+
+
function tidyPost($ret, $authorInfo) {
+
$uri = $ret->uri;
+
$post = $ret->value;
+
$embeds = [];
+
$embedType = null;
+
$replyTo = null;
+
+
if (property_exists($post, 'reply') && property_exists($post->reply, 'parent')) {
+
preg_match('/^at:\/\/(did:plc:[a-z0-9]+)\/app.bsky.feed.post\/([a-z0-9]+)$/', $post->reply->parent->uri, $atUriSplit);
+
$replyUserInfo = $this->getUserInfo($atUriSplit[1]);
+
$replyTo = $post->reply->parent->uri;/*(object) [
+
'uri' => \Smallnest\Helpers\getUrl(SERVER_URL, PRETTY_URLS, [
+
'p' => $replyUserInfo->handle,
+
'post' => $atUriSplit[2]
+
]),
+
'author' => $this->getUserInfo($atUriSplit[1])
+
];*/
+
}
+
+
if (property_exists($post, 'embed') && property_exists($post->embed, '$type')) {
+
if ($post->embed->{'$type'} === 'app.bsky.embed.images#view') {
+
$embeds = $post->embed->images;
+
$embedType = 'images';
+
} elseif ($post->embed->{'$type'} === 'app.bsky.embed.video#view') {
+
$embeds = [(object) [
+
"video" => $post->embed->cid,
+
"thumb" => $post->embed->thumbnail
+
]];
+
$embedType = 'video';
+
} elseif ($post->embed->{'$type'} === 'app.bsky.embed.external#view') {
+
$embeds = [$post->embed->external];
+
$embedtype = 'external';
+
} elseif ($post->embed->{'$type'} === 'app.bsky.embed.record') {
+
$embedtype = 'record';
+
$embeds = [$post->embed->record->uri];
+
}
+
}
+
+
return (object) [
+
'uri' => $ret->uri,
+
"author" => $authorInfo,
+
"createdAt" => $post->createdAt,
+
"text" => $post->text,
+
'replyCount' => $this->getReplies($uri),
+
'repostCount' => $this->getReposts($uri),
+
'likeCount' => $this->getLikes($uri),
+
'quoteCount' => $this->getQuotes($uri),
+
'embedType' => $embedType,
+
'embeds' => $embeds,
+
'reply' => $replyTo
+
];
+
}
+
function getUserPosts($user, $cursor = null, $newer = false) {
[$provider, $did, $handle] = $this->getUserDidAndProvider($user);
$didInfo = $this->getUserInfo($did);
-
$cursorString = !$newer && $cursor !== null ? "&cursor=$cursor" : "";
-
$userData = json_decode(file_get_contents($this->bskyApiBase.$this->apiEndpoints['getUserPosts']."?actor=".$user.$cursorString));
-
$newCursor = $userData->cursor;
-
$feed = $userData->feed;
+
$pdsData = $this->getPdsData($provider, 'com.atproto.repo.listRecords', [
+
'repo' => $did,
+
'collection' => 'app.bsky.feed.post',
+
'limit' => 100,
+
'reverse' => true,
+
'cursor' => $cursor
+
]);
+
+
$newCursor = $pdsData->cursor;
+
$feed = array_map(function($post) use ($user) {
+
$authorInfo = $this->getUserInfo($user);
+
return $this->tidyPost($post, $authorInfo);
+
}, $pdsData->records);
+
//$feed = $pdsData->records;
return $feed;
//$this->sanitizeFeed($feed, $newCursor, $cursor, $newer);
}
···
return;
}
-
$feedData = $this->getConstellationLinkData($atUri, "app.bsky.feed.post", ".embed.record.uri", $limit, $cursor);
+
//$feedData = $this->getConstellationLinkData($atUri, "app.bsky.feed.post", ".embed.record.uri", $limit, $cursor);
+
preg_match('/^at:\/\/(did:plc:[a-z0-9]+)\/[a-zA-Z0-9\.]+\/[a-z0-9]+$/', $atUri, $uriParts);
+
[$pds, $did, $handle] = $this->getPlcInfoFromRecord($uriParts[1]);
+
+
$feedData = $this->getPdsData($pds, 'app.bsky.feed.getFeed', [
+
'feed' => FRONTPAGE_FEED,
+
'limit' => $limit,
+
'cursor' => $cursor
+
]);
+
if ($feedData) {
$newCursor = $feedData->cursor;
···
}
$embeds = [];
$embedType = null;
-
print_r($post);
+
//print_r($post);
if (property_exists($post, 'embed') && property_exists($post->embed, '$type')) {
if ($post->embed->{'$type'} === 'app.bsky.embed.images#view') {
$embeds = $post->embed->images;
+7
lib/helpers.php
···
return $_COOKIE['sbs_theme'];
}
return null;
+
}
+
+
function getFont() {
+
if (array_key_exists('sbs_font', $_COOKIE)) {
+
return $_COOKIE['sbs_font'];
+
}
+
return null;
}
+31
pages/post.php
···
+
<?php
+
require_once('config.php');
+
require_once('lib/bskyToucher.php');
+
+
use Smallnest\Bsky\BskyToucher;
+
+
$bskyToucher = new BskyToucher();
+
[$provider, $did, $handle] = $bskyToucher->getUserDidAndProvider($_GET['handle']);
+
$post = $bskyToucher->getPost($did, $_GET['postId']);
+
$bskyToucher->renderPost($post);
+
$replies = $bskyToucher->getReplyRecords($post);
+
$quotes = $bskyToucher->getQuoteRecords($post);
+
$likes = $bskyToucher->getLikeUsers($post);
+
$reposts = $bskyToucher->getRepostUsers($post);
+
?>
+
<div id="replies">
+
+
</div>
+
<div id="quotes" popover>
+
+
</div>
+
<div id="likes" popover>
+
<?php if ($likes) : ?>
+
<?php foreach($likes as $like) : ?>
+
+
<?php endforeach; ?>
+
<?php endif; ?>
+
</div>
+
<div id="reposts" popover>
+
+
</div>
-1
pages/profile.php
···
[$provider, $did, $handle] = $bskyToucher->getUserDidAndProvider($_GET['handle']);
$userInfo = $bskyToucher->getUserInfo($_GET['handle']);
$userPosts = $bskyToucher->getUserPosts($_GET['handle']);
-
print_r($userInfo);
?>
<div class="profileCard">