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

put a bunch of stuff into javascript async calls

+5
css/_partials/_post.scss
···
padding: 10px;
width: 100%;
.postHeader {
display: grid;
grid-template-areas: "avatar displayName"
···
padding: 10px;
width: 100%;
+
&[data-status="unloaded"],
+
&[data-status="loading"] {
+
display: none;
+
}
+
.postHeader {
display: grid;
grid-template-areas: "avatar displayName"
+89 -35
index.php
···
$latte->render('./templates/home.latte', array_merge(Flight::get('standardParams'), [
'mainClass' => 'home feed',
'feedInfo' => $feedInfo,
-
'posts' => $posts->feed,
'cursor' => $posts->cursor,
'feedAtUri' => FRONTPAGE_FEED,
'ogtitle' => SITE_TITLE,
···
Flight::route('/u/@handle:[a-z0-9\.]+/@rkey:[a-z0-9]+', function (string $handle, string $rkey): void {
$bskyToucher = new BskyToucher();
-
$post = $bskyToucher->getPost($handle, $rkey, Flight::get('userAuth') === null);
-
$atUri = 'at://'.$post->author->did.'/app.bsky.feed.post/'.$rkey;
$latte = new Latte\Engine;
$latte->render('./templates/single.latte', array_merge(Flight::get('standardParams'), [
'mainClass' => 'post',
-
'post' => $post,
-
'ogtitle' => SITE_TITLE." | ".$post->author->displayName." (@".$post->author->handle.")",
-
'ogdesc' => $post->content,
-
'ogimage' => getPostOgImage($post),
'ogurl' => SITE_DOMAIN.'/u/'.$handle.'/'.$rkey
]));
});
···
$latte->render('./templates/profile.latte', array_merge(Flight::get('standardParams'), [
'mainClass' => 'profile',
'handle' => $handle,
-
'posts' => $posts,
'user' => $user,
'ogtitle' => SITE_TITLE." | ".$user->displayName." (@".$user->handle.")",
'ogdesc' => $user->description,
···
$latte = new Latte\Engine;
$latte->render('./templates/feed.latte', array_merge(Flight::get('standardParams'), [
'mainClass' => 'feed',
-
'posts' => $posts->feed,
'cursor' => $posts->cursor,
'feedName' => $feedInfo->title,
'feedAvatar' => $feedInfo->avatar,
···
]));
});
-
Flight::route('/api/@endpoint', function (string $endpoint): void {
$bskyToucher = new BskyToucher();
-
$latte = new Latte\Engine;
-
if ($endpoint === 'userPosts') {
-
if (!array_key_exists('user', $_GET)) Flight::json(["error" => "missing user in query"]);
-
if (!preg_match('/^did:plc:([a-z0-9]+)$/', $_GET['user'], $_out)) Flight::json(["error" => "malformed user DID"]);
-
$user = $_GET['user'];
-
$auth = false; // TODO: set up so that it's like. actually dynamic later
-
$cursor = array_key_exists('cursor', $_GET) ? $_GET['cursor'] : 0;
-
$posts = $bskyToucher->getUserPosts($user, $cursor, $auth);
-
$latte->render('./templates/_partials/feedPosts.latte', array_merge(Flight::get('standardParams'), [
-
'posts' => $posts
]));
-
return;
-
} else if ($endpoint === 'feedPosts') {
-
if (!array_key_exists('feed', $_GET)) Flight::json(["error" => "missing feed uri in query"]);
-
if (!preg_match('/^at:\/\/(did:plc:[a-z0-9]+)\/app.bsky.feed.generator\/([a-z0-9]+)$/', $_GET['feed'], $_out)) Flight::json(["error" => "malformed at-uri representing feed", "feed" => $_GET['feed']]);
-
$cursor = array_key_exists('cursor', $_GET) ? $_GET['cursor'] : 0;
-
$auth = false; // TODO: again. once i figure out auth
-
$posts = $bskyToucher->getFeed($_GET['feed'], $cursor, $auth);
-
if ($posts) {
-
$html = $latte->renderToString('./templates/_partials/feedPosts.latte', array_merge(Flight::get('standardParams'), [
-
'posts' => $posts->feed
-
]));
-
Flight::json(['posts' => $html, 'cursor' => $posts->cursor]);
-
}
-
} else if ($endpoint === 'notifs') {
-
}
});
Flight::route('/@page', function (string $page): void {
···
$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,
···
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' => SITE_DOMAIN.'/u/'.$handle.'/'.$rkey
]));
});
···
$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,
···
$latte = new Latte\Engine;
$latte->render('./templates/feed.latte', array_merge(Flight::get('standardParams'), [
'mainClass' => 'feed',
+
'posts' => array_map(function ($p) { return $p->uri; }, $posts->feed),
'cursor' => $posts->cursor,
'feedName' => $feedInfo->title,
'feedAvatar' => $feedInfo->avatar,
···
]));
});
+
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 {
+49
js/post.mjs
···
document.addEventListener('click', (e) => {
if (e.target.closest('.post .postSharing a')) {
e.preventDefault();
···
const url = link.getAttribute('data-share');
navigator.clipboard.writeText(url);
}
});
···
+
const loadPost = async (post) => {
+
post.setAttribute('data-status', 'loading');
+
const urlComponents = post.getAttribute('data-uri').split(/at:\/\/(did:plc:[a-z0-9]+)\/app\.bsky\.feed\.post\/([a-z0-9]+)/);
+
fetch('/api/post/'+urlComponents[1]+'/'+urlComponents[2])
+
.then((data) => {
+
data.text().then((content) => {
+
post.innerHTML = content;
+
post.setAttribute('data-status', 'loaded');
+
Promise.all([
+
fetch('/api/likes/'+urlComponents[1]+"/"+urlComponents[2]),
+
fetch('/api/replies/'+urlComponents[1]+"/"+urlComponents[2]),
+
fetch('/api/reposts/'+urlComponents[1]+"/"+urlComponents[2]),
+
fetch('/api/quotes/'+urlComponents[1]+"/"+urlComponents[2])
+
]).then((values) => {
+
values[0].json().then((likes) => {
+
post.querySelector('.like-count').textContent = likes.total;
+
if (document.getElementById('interactions')) {
+
document.getElementById('likes').querySelector('.inner').innerHTML = likes.rendered;
+
}
+
});
+
values[1].json().then((replies) => {
+
post.querySelector('.reply-count').textContent = replies.total;
+
if (document.getElementById('interactions')) {
+
document.getElementById('replies').querySelector('.inner').innerHTML = replies.rendered;
+
}
+
});
+
values[2].json().then((reposts) => {
+
post.querySelector('.repost-count').textContent = reposts.total;
+
if (document.getElementById('interactions')) {
+
document.getElementById('reposts').querySelector('.inner').innerHTML = reposts.rendered;
+
}
+
});
+
values[3].json().then((quotes) => {
+
post.querySelector('.quote-count').textContent = quotes.total;
+
if (document.getElementById('interactions')) {
+
document.getElementById('quotes').querySelector('.inner').innerHTML = quotes.rendered;
+
}
+
});
+
})
+
});
+
});
+
}
+
document.addEventListener('click', (e) => {
if (e.target.closest('.post .postSharing a')) {
e.preventDefault();
···
const url = link.getAttribute('data-share');
navigator.clipboard.writeText(url);
}
+
});
+
+
window.addEventListener('load', (_e) => {
+
document.querySelectorAll('[data-status="unloaded"]').forEach(async (post) => {
+
loadPost(post);
+
});
});
+3 -88
lib/bskyToucher.php
···
if (!$ret || $ret->getReasonPhrase() !== 'OK') return false;
$body = json_decode((string) $ret->getBody());
if (!$body->feed || count($body->feed) === 0) return [];
-
/*$ret = array_map(function($rec) {
-
preg_match('/at:\/\/(did:plc:[a-z0-9]+)\/app.bsky.feed.post\/([a-z0-9]+)/', $rec->post, $uriComponents);
-
$did = $uriComponents[1];
-
$rkey = $uriComponents[2];
-
return $this->getPost($did, $rkey, true);
-
}, $body->feed);*/
-
// map version
-
$ret = await(map(
-
$body->feed,
-
function ($rec) {
-
preg_match('/at:\/\/(did:plc:[a-z0-9]+)\/app.bsky.feed.post\/([a-z0-9]+)/', $rec->post, $uriComponents);
-
$did = $uriComponents[1];
-
$rkey = $uriComponents[2];
-
return $this->getPost($did, $rkey, true);
-
}
-
), 10);
-
// batch version
-
/*$ret = await(batch(
-
$body->feed,
-
function ($batch) {
-
return async(function () use ($batch) {
-
return array_map(function($rec) {
-
preg_match('/at:\/\/(did:plc:[a-z0-9]+)\/app.bsky.feed.post\/([a-z0-9]+)/', $rec->post, $uriComponents);
-
$did = $uriComponents[1];
-
$rkey = $uriComponents[2];
-
return $this->getPost($did, $rkey, true);
-
}, $batch);
-
});
-
}, 25, 2));*/
-
$body->feed = $ret;
return $body;
}
···
];
}
-
function getUserPosts(string $did, ?string $cursor = null, bool $auth = false, bool $newer = false): array|bool {
$userData = $this->getUserInfo($did, 'did');
-
if (!$userData) return [];
$postData = await(async(function () use ($did, $cursor, $userData) {
return $this->getPdsData($userData->pds, 'com.atproto.repo.listRecords', [
'repo' => $did,
···
]);
}));
if (!$postData) return false;
-
if (count($postData->records) === 0) return $postData;
-
try {
-
$posts = await(all(array_map(function ($post) {
-
return async(function () use ($post) {
-
$uri = $this->splitAtUri($post->uri);
-
$cache = \requestPostCache($uri->rkey);
-
if ($cache) return $this->sanitizeCachedPost($cache);
-
return $this->sanitizeSlingshotRecordPost($post);
-
});
-
}, $postData->records)));
-
return $posts;
-
} catch (Exception $e) {
-
echo "<!--";
-
print_r($e);
-
echo "-->";
-
return false;
-
}
}
function getSearchResults(string $query):array {
···
$rkey = $uriComponents[2];
$authorInfo = $this->getUserInfo($did, 'did');
$facets = property_exists($post->value, 'facets') ? $this->sanitizeFacets($post->value->facets) : [];
-
$interactions = $this->getInteractions($post->uri, $getReplies);
-
$replies = $interactions->replies;
-
$likes = $interactions->likes;
-
$reposts = $interactions->reposts;
-
$quotes = $interactions->quotes;
$ret = (object) [
'author' => (object) [
'displayName' => $authorInfo->displayName,
···
'postId' => $rkey,
'postLink' => '/u/'.$authorInfo->handle.'/'.$rkey,
'content' => property_exists($post->value, 'text') ? $this->applyFacets($post->value->text, $facets) : '',
-
'replyCount' => $replies->total,
-
'replies' => $replies->records,
-
'repostCount' => $reposts->total,
-
'reposts' => $reposts->records,
-
'likeCount' => $likes->total,
-
'likes' => $likes->records,
-
'quoteCount' => $quotes->total,
-
'quotes' => $quotes->records,
'createdAt' => $post->value->createdAt,
'embedType' => property_exists($post->value, 'embed') ? $post->value->embed->{'$type'} : null,
'embeds' => property_exists($post->value, 'embed') ? $this->sanitizeEmbeds($post->value->embed, $authorInfo) : [],
···
$processedText = $this->applyFacets($post->record->text, $facets);
$embedType = property_exists($post->record, 'embed') ? $post->record->embed->{'$type'} : '';
$embeds = property_exists($post->record, 'embed') ? $this->sanitizeEmbeds($post->record->embed, $authorData) : [];
-
$interactions = $this->getInteractions($post->uri, $getReplies);
-
$replies = $interactions->replies;
-
$reposts = $interactions->reposts;
-
$likes = $interactions->likes;
-
$quotes = $interactions->quotes;
\updatePostCache($rkeyMatch[2], $authorData->did, $processedText, $embedType, json_encode($embeds), $post->record->createdAt);
return (object) [
'author' => (object) [
···
'pds' => $authorData->pds,
'postId' => $rkeyMatch[2],
'postLink' => '/u/'.$authorData->handle.'/'.$rkeyMatch[2],
-
'replyCount' => $replies->total,
-
'replies' => $replies->records,
-
'repostCount' => $reposts->total,
-
'reposts' => $reposts->records,
-
'likeCount' => $likes->total,
-
'likes' => $likes->records,
-
'quoteCount' => $quotes->total,
-
'quotes' => $quotes->records,
'createdAt' => $post->record->createdAt,
'embedType' => $embedType,
'embeds' => $embeds
···
function sanitizeCachedPost(object $post, bool $getReplies = true): object {
$uri = 'at://'.$post->did.'/app.bsky.feed.post/'.$post->rkey;
$authorData = $this->getUserInfo($post->did, 'did');
-
$interactions = $this->getInteractions($uri);
-
$likes = $interactions->likes;
-
$replies = $interactions->replies;
-
$reposts = $interactions->reposts;
-
$quotes = $interactions->quotes;
return (object) [
'author' => (object) [
'displayName' => $authorData->displayName,
···
'postLink' => '/u/'.$authorData->handle.'/'.$post->rkey,
'content' => $post->text,
'createdAt' => $post->createdAt,
-
'replyCount' => $replies->total,
-
'replies' => $replies->records,
-
'repostCount' => $reposts->total,
-
'reposts' => $reposts->records,
-
'likeCount' => $likes->total,
-
'likes' => $likes->records,
-
'quoteCount' => $quotes->total,
-
'quotes' => $quotes->records,
'embedType' => $post->embedType,
'embeds' => json_decode($post->embeds)
];
···
if (!$ret || $ret->getReasonPhrase() !== 'OK') return false;
$body = json_decode((string) $ret->getBody());
if (!$body->feed || count($body->feed) === 0) return [];
return $body;
}
···
];
}
+
function getUserPosts(string $did, ?string $cursor = null, bool $auth = false, bool $newer = false): object|bool {
$userData = $this->getUserInfo($did, 'did');
+
if (!$userData) return false;
$postData = await(async(function () use ($did, $cursor, $userData) {
return $this->getPdsData($userData->pds, 'com.atproto.repo.listRecords', [
'repo' => $did,
···
]);
}));
if (!$postData) return false;
+
return $postData;
}
function getSearchResults(string $query):array {
···
$rkey = $uriComponents[2];
$authorInfo = $this->getUserInfo($did, 'did');
$facets = property_exists($post->value, 'facets') ? $this->sanitizeFacets($post->value->facets) : [];
$ret = (object) [
'author' => (object) [
'displayName' => $authorInfo->displayName,
···
'postId' => $rkey,
'postLink' => '/u/'.$authorInfo->handle.'/'.$rkey,
'content' => property_exists($post->value, 'text') ? $this->applyFacets($post->value->text, $facets) : '',
'createdAt' => $post->value->createdAt,
'embedType' => property_exists($post->value, 'embed') ? $post->value->embed->{'$type'} : null,
'embeds' => property_exists($post->value, 'embed') ? $this->sanitizeEmbeds($post->value->embed, $authorInfo) : [],
···
$processedText = $this->applyFacets($post->record->text, $facets);
$embedType = property_exists($post->record, 'embed') ? $post->record->embed->{'$type'} : '';
$embeds = property_exists($post->record, 'embed') ? $this->sanitizeEmbeds($post->record->embed, $authorData) : [];
\updatePostCache($rkeyMatch[2], $authorData->did, $processedText, $embedType, json_encode($embeds), $post->record->createdAt);
return (object) [
'author' => (object) [
···
'pds' => $authorData->pds,
'postId' => $rkeyMatch[2],
'postLink' => '/u/'.$authorData->handle.'/'.$rkeyMatch[2],
'createdAt' => $post->record->createdAt,
'embedType' => $embedType,
'embeds' => $embeds
···
function sanitizeCachedPost(object $post, bool $getReplies = true): object {
$uri = 'at://'.$post->did.'/app.bsky.feed.post/'.$post->rkey;
$authorData = $this->getUserInfo($post->did, 'did');
return (object) [
'author' => (object) [
'displayName' => $authorData->displayName,
···
'postLink' => '/u/'.$authorData->handle.'/'.$post->rkey,
'content' => $post->text,
'createdAt' => $post->createdAt,
'embedType' => $post->embedType,
'embeds' => json_decode($post->embeds)
];
+17 -31
lib/maria-db.php
···
// ok! we're good let's go
$db->close();
-
function deleteExpired(string $table): void {
$db = mysqli_init();
$db->real_connect(DB_HOST, DB_USERNAME, DB_PASS);
$db->real_query('use '.DB_NAME.';');
$current_time = strtotime('now');
$db->real_query('delete from '.$table.' where expires < '.$current_time.';');
$db->close();
}
function requestUserCache(string $value, string $field = 'handle'): ?object {
-
$db = mysqli_init();
-
$db->real_connect(DB_HOST, DB_USERNAME, DB_PASS);
-
$db->real_query('use '.DB_NAME.';');
deleteExpired('user_cache');
$result = $db->query('select * from user_cache where '.$field.'="'.$db->real_escape_string($value).'";');
if ($result->num_rows === 0) return null;
···
}
function requestMinidocCache(string $value, string $field = 'handle'): ?object {
-
$db = mysqli_init();
-
$db->real_connect(DB_HOST, DB_USERNAME, DB_PASS);
-
$db->real_query('use '.DB_NAME.';');
deleteExpired('minidoc_cache');
$result = $db->query('select * from minidoc_cache where '.$field.'="'.$db->real_escape_string($value).'";');
if ($result->num_rows === 0) return null;
···
}
function requestPostCache(string $rkey): ?object {
-
$db = mysqli_init();
-
$db->real_connect(DB_HOST, DB_USERNAME, DB_PASS);
-
$db->real_query('use '.DB_NAME.';');
deleteExpired('post_cache');
$result = $db->query('select * from post_cache where rkey="'.$db->real_escape_string($rkey).'";');
if ($result->num_rows === 0) return null;
···
}
function requestPlcCache(string $did): ?object {
-
$db = mysqli_init();
-
$db->real_connect(DB_HOST, DB_USERNAME, DB_PASS);
-
$db->real_query('use '.DB_NAME.';');
deleteExpired('plc_cache');
$result = $db->query('select * from plc_cache where did="'.$db->real_escape_string($did).'";');
if ($result->num_rows === 0) return null;
···
}
function requestFeedCache(string $at_uri): ?object {
-
$db = mysqli_init();
-
$db->real_connect(DB_HOST, DB_USERNAME, DB_PASS);
-
$db->real_query('use '.DB_NAME.';');
deleteExpired('feed_cache');
$result = $db->query('select * from feed_cache where at_uri="'.$db->real_escape_string($at_uri).'";');
if ($result->num_rows === 0) return null;
···
}
function updateUserCache(string $handle, string $did, ?string $display_name, string $pds, ?string $avatar, ?string $banner, string $description, ?string $pinned): void {
-
$db = mysqli_init();
-
$db->real_connect(DB_HOST, DB_USERNAME, DB_PASS);
-
$db->real_query('use '.DB_NAME.';');
$expires = strtotime('now') + 60*60;
if (!empty(requestUserCache($did, 'did'))) {
$db->query('update user_cache set handle="'.$db->real_escape_string($handle).'", display_name="'.$db->real_escape_string($display_name).'", description="'.$db->real_escape_string($description).'", avatar="'.$db->real_escape_string($avatar).'", banner="'.$db->real_escape_string($banner).'", pds="'.$db->real_escape_string($pds).'", pinned_post="'.$db->real_escape_string($pinned).'", expires='.$expires.' where did="'.$db->real_escape_string($did).'";');
···
}
function updateMinidocCache(string $handle, string $did, string $pds): void {
-
$db = mysqli_init();
-
$db->real_connect(DB_HOST, DB_USERNAME, DB_PASS);
-
$db->real_query('use '.DB_NAME.';');
$expires = strtotime('now') + 60*60*48;
if (!empty(requestMinidocCache($did, 'did'))) {
$db->query('update minidoc_cache set handle="'.$db->real_escape_string($handle).'", pds="'.$db->real_escape_string($pds).'", expires='.$expires.' where did="'.$db->real_escape_string($did).'";');
···
}
function updatePlcCache(string $did, string $plcdoc): void {
-
$db = mysqli_init();
-
$db->real_connect(DB_HOST, DB_USERNAME, DB_PASS);
-
$db->real_query('use '.DB_NAME.';');
$expires = strtotime('now') + 60*60*48;
if (!empty(requestMinidocCache($did))) {
$db->query('update plc_cache set plcdoc="'.$db->real_escape_string($plcdoc).'", expires='.$expires.' where did="'.$db->real_escape_string($did).'";');
···
}
function updateFeedCache(string $atUri, string $title, ?string $description, ?string $avatar, string $creator_did): void {
-
$db = mysqli_init();
-
$db->real_connect(DB_HOST, DB_USERNAME, DB_PASS);
-
$db->real_query('use '.DB_NAME.';');
$expires = strtotime('now') + 60*60*4;
if (!empty(requestFeedCache($atUri))) {
$db->query('update feed_cache set title="'.$db->real_escape_string($title).'", description="'.$db->real_escape_string($description).'", avatar="'.$db->real_escape_string($avatar).'", creator_did="'.$db->real_escape_string($creator_did).'", expires='.$expires.' where at_uri="'.$db->real_escape_string($atUri).'";');
···
}
function updatePostCache(string $rkey, string $did, string $text, string $embedType, string $embedData, string $createdAt): void {
-
$db = mysqli_init();
-
$db->real_connect(DB_HOST, DB_USERNAME, DB_PASS);
-
$db->real_query('use '.DB_NAME.';');
$dbdate = date('Y-m-d h:i:s', strtotime($createdAt));
$expires = strtotime('now') + 60*60*48;
if (!empty(requestPostCache($rkey))) {
···
// ok! we're good let's go
$db->close();
+
function newConn() {
$db = mysqli_init();
$db->real_connect(DB_HOST, DB_USERNAME, DB_PASS);
$db->real_query('use '.DB_NAME.';');
+
$db->set_charset('utf8mb4');
+
return $db;
+
}
+
+
function deleteExpired(string $table): void {
+
$db = newConn();
$current_time = strtotime('now');
$db->real_query('delete from '.$table.' where expires < '.$current_time.';');
$db->close();
}
function requestUserCache(string $value, string $field = 'handle'): ?object {
+
$db = newConn();
deleteExpired('user_cache');
$result = $db->query('select * from user_cache where '.$field.'="'.$db->real_escape_string($value).'";');
if ($result->num_rows === 0) return null;
···
}
function requestMinidocCache(string $value, string $field = 'handle'): ?object {
+
$db = newConn();
deleteExpired('minidoc_cache');
$result = $db->query('select * from minidoc_cache where '.$field.'="'.$db->real_escape_string($value).'";');
if ($result->num_rows === 0) return null;
···
}
function requestPostCache(string $rkey): ?object {
+
$db = newConn();
deleteExpired('post_cache');
$result = $db->query('select * from post_cache where rkey="'.$db->real_escape_string($rkey).'";');
if ($result->num_rows === 0) return null;
···
}
function requestPlcCache(string $did): ?object {
+
$db = newConn();
deleteExpired('plc_cache');
$result = $db->query('select * from plc_cache where did="'.$db->real_escape_string($did).'";');
if ($result->num_rows === 0) return null;
···
}
function requestFeedCache(string $at_uri): ?object {
+
$db = newConn();
deleteExpired('feed_cache');
$result = $db->query('select * from feed_cache where at_uri="'.$db->real_escape_string($at_uri).'";');
if ($result->num_rows === 0) return null;
···
}
function updateUserCache(string $handle, string $did, ?string $display_name, string $pds, ?string $avatar, ?string $banner, string $description, ?string $pinned): void {
+
$db = newConn();
$expires = strtotime('now') + 60*60;
if (!empty(requestUserCache($did, 'did'))) {
$db->query('update user_cache set handle="'.$db->real_escape_string($handle).'", display_name="'.$db->real_escape_string($display_name).'", description="'.$db->real_escape_string($description).'", avatar="'.$db->real_escape_string($avatar).'", banner="'.$db->real_escape_string($banner).'", pds="'.$db->real_escape_string($pds).'", pinned_post="'.$db->real_escape_string($pinned).'", expires='.$expires.' where did="'.$db->real_escape_string($did).'";');
···
}
function updateMinidocCache(string $handle, string $did, string $pds): void {
+
$db = newConn();
$expires = strtotime('now') + 60*60*48;
if (!empty(requestMinidocCache($did, 'did'))) {
$db->query('update minidoc_cache set handle="'.$db->real_escape_string($handle).'", pds="'.$db->real_escape_string($pds).'", expires='.$expires.' where did="'.$db->real_escape_string($did).'";');
···
}
function updatePlcCache(string $did, string $plcdoc): void {
+
$db = newConn();
$expires = strtotime('now') + 60*60*48;
if (!empty(requestMinidocCache($did))) {
$db->query('update plc_cache set plcdoc="'.$db->real_escape_string($plcdoc).'", expires='.$expires.' where did="'.$db->real_escape_string($did).'";');
···
}
function updateFeedCache(string $atUri, string $title, ?string $description, ?string $avatar, string $creator_did): void {
+
$db = newConn();
$expires = strtotime('now') + 60*60*4;
if (!empty(requestFeedCache($atUri))) {
$db->query('update feed_cache set title="'.$db->real_escape_string($title).'", description="'.$db->real_escape_string($description).'", avatar="'.$db->real_escape_string($avatar).'", creator_did="'.$db->real_escape_string($creator_did).'", expires='.$expires.' where at_uri="'.$db->real_escape_string($atUri).'";');
···
}
function updatePostCache(string $rkey, string $did, string $text, string $embedType, string $embedData, string $createdAt): void {
+
$db = newConn();
$dbdate = date('Y-m-d h:i:s', strtotime($createdAt));
$expires = strtotime('now') + 60*60*48;
if (!empty(requestPostCache($rkey))) {
+2 -2
templates/_partials/feedPosts.latte
···
{if $posts}
{foreach $posts as $post}
-
{include 'post.latte', post: $post}
{/foreach}
{else}
-
<div class="noposts">There was an error retrieving posts. You can try refreshing the page, or otherwise filing a bug report.</div>
{/if}
···
{if $posts}
{foreach $posts as $post}
+
{include 'post_shell.latte', post: $post}
{/foreach}
{else}
+
<div class="noposts">no posts here!</div>
{/if}
+5
templates/_partials/interaction_list.latte
···
···
+
{if $interactions}
+
{foreach $interactions as $itx}
+
{include 'profile_mini.latte', displayName: $itx->displayName, handle: $itx->handle, did: $itx->did, avatar: $itx->avatar, profileLink: '/u/'.$itx->handle}
+
{/foreach}
+
{/if}
+27 -31
templates/_partials/post.latte
···
-
{if $post}
-
<div class="post" data-cursor="{$post->postId}" data-did="{$post->author->did}">
-
<div class="postHeader">
-
<div class="avatar">
-
<a href="{$post->author->profileLink}"><img src="{$post->author->avatar}" alt="{$post->author->displayName}'s user icon" /></a>
-
</div>
-
<div class="displayName"><a href="{$post->author->profileLink}">{$post->author->displayName|noescape}</a></div>
-
<div class="handle"><a href="{$post->author->profileLink}">{$post->author->handle}</a></div>
-
<div class="timeAgo"><a href="{$post->postLink}">{$post->createdAt}</a></div>
-
</div>
-
<div class="postContent">{$post->content|noescape}</div>
-
{if $post->embedType === 'app.bsky.embed.images'}
-
{include 'embed_image.latte', embeds: $post->embeds, postId: $post->postId}
-
{elseif $post->embedType === 'app.bsky.embed.video'}
-
{include 'embed_video.latte', embed: $post->embeds[0]}
-
{elseif $post->embedType === 'app.bsky.embed.external'}
-
{include 'embed_link.latte', embed: $post->embeds[0]}
-
{elseif $post->embedType === 'app.bsky.embed.record'}
-
{include 'embed_record.latte', embed: $post->embeds[0]}
-
{/if}
-
<div class="postFooter">
-
<div class="actions">
-
<button type="button" data-action="like">like ({$post->likeCount})</button>
-
<button type="button" data-action="like">reply ({$post->replyCount})</button>
-
<button type="button" data-action="quote">quote ({$post->quoteCount})</button>
-
<button type="button" data-action="repost">repost ({$post->repostCount})</button>
-
</div>
-
<div class="postSharing">
-
<a href="#" data-share="{$post->postLink}">copy link</a>
-
</div>
</div>
</div>
-
{/if}
···
+
<div class="postHeader">
+
<div class="avatar">
+
<a href="{$post->author->profileLink}"><img src="{$post->author->avatar}" alt="{$post->author->displayName}'s user icon" /></a>
</div>
+
<div class="displayName"><a href="{$post->author->profileLink}">{$post->author->displayName|noescape}</a></div>
+
<div class="handle"><a href="{$post->author->profileLink}">{$post->author->handle}</a></div>
+
<div class="timeAgo"><a href="{$post->postLink}">{$post->createdAt}</a></div>
</div>
+
<div class="postContent">{$post->content|noescape}</div>
+
{if $post->embedType === 'app.bsky.embed.images'}
+
{include 'embed_image.latte', embeds: $post->embeds, postId: $post->postId}
+
{elseif $post->embedType === 'app.bsky.embed.video'}
+
{include 'embed_video.latte', embed: $post->embeds[0]}
+
{elseif $post->embedType === 'app.bsky.embed.external'}
+
{include 'embed_link.latte', embed: $post->embeds[0]}
+
{elseif $post->embedType === 'app.bsky.embed.record'}
+
{include 'embed_record.latte', embed: $post->embeds[0]}
+
{/if}
+
<div class="postFooter">
+
<div class="actions">
+
<button type="button" data-action="like">like (<span class="like-count"></span>)</button>
+
<button type="button" data-action="like">reply (<span class="reply-count"></span>)</button>
+
<button type="button" data-action="quote">quote (<span class="quote-count"></span>)</button>
+
<button type="button" data-action="repost">repost (<span class="repost-count"></span>)</button>
+
</div>
+
<div class="postSharing">
+
<a href="#" data-share="{$post->postLink}">copy link</a>
+
</div>
+
</div>
+5
templates/_partials/post_shell.latte
···
···
+
{if $post}
+
<div class="post" data-uri="{$post}" data-status="unloaded">
+
+
</div>
+
{/if}
+4 -33
templates/single.latte
···
{layout 'layout.latte'}
-
{block title} | post by {$post->author->displayName} (@{$post->author->handle}){/block}
{block content}
-
{include '_partials/post.latte', post: $post}
<div id="interactions">
<button type="button" popovertarget="likes" class="btn-invis">See Likes</button> <button type="button" popovertarget="reposts" class="btn-invis">See Reposts</button> <button type="button" popovertarget="quotes" class="btn-invis">See Quotes</button>
</div>
<div id="likes" popover>
<div class="inner">
-
<div n:if="$post->likeCount === 0">
-
<p>No likes yet!</p>
-
</div>
-
{if $post->likes}
-
{foreach $post->likes as $like}
-
{include '_partials/profile_mini.latte', displayName: $like->displayName, handle: $like->handle, did: $like->did, avatar: $like->avatar, profileLink: '/u/'.$like->handle}
-
{/foreach}
-
{/if}
</div>
</div>
<div id="reposts" popover>
<div class="inner">
-
<div n:if="$post->repostCount === 0">
-
<p>No reposts yet!</p>
-
</div>
-
{if $post->reposts}
-
{foreach $post->reposts as $repost}
-
{include '_partials/profile_mini.latte', displayName: $repost->displayName, handle: $repost->handle, did: $repost->did, avatar: $repost->avatar, profileLink: '/u/'.$repost->handle}
-
{/foreach}
-
{/if}
</div>
</div>
<div id="quotes" popover>
<div class="inner">
-
<div n:if="$post->quoteCount === 0">
-
<p>No quotes yet!</p>
-
</div>
-
{if $post->quotes}
-
{foreach $post->quotes as $quote}
-
{include '_partials/post.latte', post: $quote}
-
{/foreach}
-
{/if}
</div>
</div>
<div id="replies">
<h2>Replies</h2>
-
<div n:if="$post->replyCount === 0">
-
<p>No replies yet!</p>
</div>
-
{if $post->replies}
-
{foreach $post->replies as $reply}
-
{include '_partials/post.latte', post: $reply}
-
{/foreach}
-
{/if}
</div>
{/block}
···
{layout 'layout.latte'}
+
{block title} | post by {$displayName} (@{$handle}){/block}
{block content}
+
{include '_partials/post_shell.latte', post: $post}
<div id="interactions">
<button type="button" popovertarget="likes" class="btn-invis">See Likes</button> <button type="button" popovertarget="reposts" class="btn-invis">See Reposts</button> <button type="button" popovertarget="quotes" class="btn-invis">See Quotes</button>
</div>
<div id="likes" popover>
<div class="inner">
</div>
</div>
<div id="reposts" popover>
<div class="inner">
</div>
</div>
<div id="quotes" popover>
<div class="inner">
</div>
</div>
<div id="replies">
<h2>Replies</h2>
+
<div class="inner">
+
</div>
</div>
{/block}