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

new feed posts acquisition direct from generator

+2
css/_partials/_post.scss
···
img {
border: none;
display: block;
+
height: auto;
+
width: 100%;
}
}
+1
css/main.scss
···
@use 'themes/rosepine';
@use 'themes/solarized';
@use 'themes/tempus';
+
@use 'themes/seoul';
@use '_partials/_fonts';
@use '_partials/_globals';
+33
css/themes/seoul.scss
···
+
button[data-theme="seoul"] {
+
background-color: #faf4ed;
+
background-image: linear-gradient(to bottom right, #faf4ed, #d7827e);
+
+
@media (prefers-color-scheme: dark) {
+
background-color: #232136;
+
background-image: linear-gradient(to bottom right, #232136, #ea9a97);
+
}
+
}
+
+
body[data-theme="seoul"] {
+
--border-color: #4e4e4e;
+
--background-color: #eeeeee;
+
--text-color: #4e4e4e;
+
--link-color: #008787;
+
--btn-primary-bg: #005f00;
+
--btn-primary-color: #eeeeee;
+
--btn-danger-bg: #870100;
+
--btn-danger-color: #eeeeee;
+
--blend-color: #eeeeee;
+
+
@media (prefers-color-scheme: dark) {
+
--border-color: #626262;
+
--background-color: #4e4e4e;
+
--text-color: #e0def4;
+
--link-color: #87d7d7;
+
--btn-primary-bg: #87af87;
+
--btn-primary-color: #4e4e4e;
+
--btn-danger-bg: #d75f87;
+
--btn-danger-color: #4e4e4e;
+
--blend-color: #4e4e4e;
+
}
+
}
+4 -4
css/themes/tempus.scss
···
}
body[data-theme="tempus"] {
-
--border-color: #a83884;
+
--border-color: #796271;
--background-color: #f8f2e5;
--text-color: #464340;
-
--link-color: #385dc4;
+
--link-color: #a83884;
--btn-primary-bg: #007070;
--btn-primary-color: #f8f2e5;
--btn-danger-bg: #c81000;
···
--blend-color: #f8f2e5;
@media (prefers-color-scheme: dark) {
-
--border-color: #2c3150;
+
--border-color: #a7a2c4;
--background-color: #1f252d;
--text-color: #a2a8ba;
-
--link-color: #2ab7bb;
+
--link-color: #e58f84;
--btn-primary-bg: #80b48f;
--btn-primary-color: #a2a8ba;
--btn-danger-bg: #cb8d56;
+15 -9
index.php
···
$posts = $bskyToucher->getFeed(Flight::get('frontpageFeed'));
$latte = new Latte\Engine;
$latte->render('./templates/home.latte', array_merge(Flight::get('standardParams'), [
-
'mainClass' => 'home',
-
'posts' => $posts
+
'mainClass' => 'home feed',
+
'posts' => $posts->feed,
+
'cursor' => $posts->cursor,
+
'feedAtUri' => FRONTPAGE_FEED
]));
});
···
$latte = new Latte\Engine;
$latte->render('./templates/feed.latte', array_merge(Flight::get('standardParams'), [
'mainClass' => 'feed',
-
'posts' => $posts,
+
'posts' => $posts->feed,
+
'cursor' => $posts->cursor,
'feedName' => $feedInfo->title,
'feedAvatar' => $feedInfo->avatar,
'feedDescription' => $feedInfo->description,
···
$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, $auth, $cursor);
+
$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"]);
+
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);
-
/*$latte->render('./templates/_partials/feedPosts.latte', array_merge(Flight::get('standardParams'), [
-
'posts' => $posts
-
]));*/
+
$posts = $bskyToucher->getFeed($_GET['feed'], $cursor, $auth);
+
if ($posts) {
+
$latte->render('./templates/_partials/feedPosts.latte', array_merge(Flight::get('standardParams'), [
+
'posts' => $posts->feed,
+
'cursor' => $posts->cursor
+
]));
+
}
} else if ($endpoint === 'notifs') {
}
+28 -17
js/feed.mjs
···
-
document.querySelector('.refreshBar').addEventListener('click', async (e) => {
-
e.preventDefault();
-
const feedUri = e.target.getAttribute('data-feed');
-
const cursor = document.querySelector('.post').getAttribute('data-cursor');
-
console.log(cursor);
-
fetch('lib/asyncRequestHandler.php', {
-
method: 'POST',
-
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
-
body: "action=updateFeed&feed="+feedUri+"&cursor="+cursor+"&newer=true",
-
mode: 'cors'
-
}).then(response => response.text())
-
.then((data) => {
-
const htmlNodes = new DOMParser().parseFromString(data, 'text/html');
-
const refreshBar = document.querySelector('.refreshBar');
-
refreshBar.after(...htmlNodes.querySelectorAll('.post'));
-
});
-
});
+
if (document.querySelector('main').classList.contains('feed') && document.querySelectorAll('.postsList > .post').length > 0) {
+
const observerCheck = (entry, observer) => {
+
if (entry[0].isIntersecting) {
+
const container = document.querySelector('.postsList');
+
const lastPost = document.querySelector('.postsList > .post:last-child');
+
const uri = container.getAttribute('data-uri');
+
const cursor = lastPost.querySelector(".timeAgo").textContent;
+
const params = new URLSearchParams();
+
params.append("feed", uri);
+
params.append("cursor", cursor);
+
observer.unobserve(lastPost);
+
fetch(`/api/feedPosts?${params}`)
+
.then((data) => {
+
data.text().then((content) => {
+
container.innerHTML += content;
+
const newobserver = new IntersectionObserver(observerCheck);
+
newobserver.observe(document.querySelector('.postsList > .post:last-child'));
+
});
+
}, (err) => {
+
console.error(err);
+
const newobserver = new IntersectionObserver(observerCheck);
+
newobserver.observe(document.querySelector('.postsList > .post:last-child'));
+
});
+
}
+
}
+
const observer = new IntersectionObserver(observerCheck);
+
observer.observe(document.querySelector('.postsList > .post:last-child'));
+
}
+1
js/main.mjs
···
import './themes.mjs';
import './post.mjs';
import './feed.mjs';
+
import './refresh.mjs';
import './userposts.mjs';
+19
js/refresh.mjs
···
+
if (document.querySelector('.refreshBar')) {
+
document.querySelector('.refreshBar').addEventListener('click', async (e) => {
+
e.preventDefault();
+
const feedUri = e.target.getAttribute('data-feed');
+
const cursor = document.querySelector('.post').getAttribute('data-cursor');
+
console.log(cursor);
+
fetch('lib/asyncRequestHandler.php', {
+
method: 'POST',
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
+
body: "action=updateFeed&feed="+feedUri+"&cursor="+cursor+"&newer=true",
+
mode: 'cors'
+
}).then(response => response.text())
+
.then((data) => {
+
const htmlNodes = new DOMParser().parseFromString(data, 'text/html');
+
const refreshBar = document.querySelector('.refreshBar');
+
refreshBar.after(...htmlNodes.querySelectorAll('.post'));
+
});
+
});
+
}
+19 -17
js/userposts.mjs
···
if (document.querySelector('main').classList.contains("profile") && document.querySelectorAll('.postsList > .post').length > 0) {
const observerCheck = (entry, observer) => {
-
const container = document.querySelector('.postsList');
-
const lastPost = document.querySelector('.postsList > .post:last-child');
-
const cursor = lastPost.getAttribute('data-cursor');
-
const user = lastPost.getAttribute('data-did');
-
const params = new URLSearchParams();
-
params.append("user", user);
-
params.append("cursor", cursor);
-
observer.unobserve(lastPost);
-
fetch(`/api/userPosts?${params}`)
-
.then((data) => {
-
data.text().then((content) => {
-
container.innerHTML += content;
+
if (entry[0].isIntersecting) {
+
const container = document.querySelector('.postsList');
+
const lastPost = document.querySelector('.postsList > .post:last-child');
+
const cursor = lastPost.getAttribute('data-cursor');
+
const user = lastPost.getAttribute('data-did');
+
const params = new URLSearchParams();
+
params.append("user", user);
+
params.append("cursor", cursor);
+
observer.unobserve(lastPost);
+
fetch(`/api/userPosts?${params}`)
+
.then((data) => {
+
data.text().then((content) => {
+
container.innerHTML += content;
+
const newobserver = new IntersectionObserver(observerCheck);
+
newobserver.observe(document.querySelector('.postsList > .post:last-child'));
+
});
+
}, (err) => {
+
console.error(err);
const newobserver = new IntersectionObserver(observerCheck);
newobserver.observe(document.querySelector('.postsList > .post:last-child'));
});
-
}, (err) => {
-
console.error(err);
-
const newobserver = new IntersectionObserver(observerCheck);
-
newobserver.observe(document.querySelector('.postsList > .post:last-child'));
-
});
+
}
}
const observer = new IntersectionObserver(observerCheck);
observer.observe(document.querySelector('.postsList > .post:last-child'));
+54 -21
lib/bskyToucher.php
···
/* GENERAL QUERIES */
-
function makeRequest(string $method, string $uri, array $opts = [], float $timeout = 5.0, int $maxRetries = 5): ResponseInterface {
+
function makeRequest(string $method, string $uri, array $opts = [], float $timeout = 5.0, int $maxRetries = 5): ResponseInterface|Exception {
global $log;
try {
$handler = HandlerStack::create();
···
$response = $httpClient->request($method, $uri, $opts);
return $response;
} catch (Exception $e) {
-
error_log($e);
-
return false;
+
return $e;
}
}
-
function getSlingshotData(string $repo, string $collection, string $rkey, bool $reverse = false): object|bool {
+
function getSlingshotData(string $repo, string $collection, ?string $rkey = 'self', ?array $params = []): object|bool {
$query = [
'collection' => $collection,
-
'repo' => $repo,
-
'rkey' => $rkey
+
'repo' => $repo
];
-
if ($reverse) {
-
$query['reverse'] = true;
+
if ($rkey) {
+
$query['rkey'] = $rkey;
+
}
+
if ($params) {
+
$query = array_merge($query, $params);
}
$ret = $this->makeRequest("GET", $this->slingshotBase."com.atproto.repo.getRecord", [
'query' => $query
···
return false;
}
+
function getFeedSkeleton(string $didweb, string $atUri, ?string $offset = null): object|bool {
+
$query = [
+
'feed' => $atUri
+
];
+
if ($offset) {
+
$query['offset'] = $offset;
+
}
+
preg_match('/^did:web:([a-z0-9\.\-]+)$/', $didweb, $didWebBase);
+
if (!$didWebBase) return false;
+
$base = "https://".$didWebBase[1];
+
$ret = $this->makeRequest('GET', $base.'/xrpc/app.bsky.feed.getFeedSkeleton?feed='.$atUri);
+
if (gettype($ret) === 'Exception') {
+
return false;
+
}
+
$body = json_decode($ret->getBody());
+
$body->feed = array_map(function ($feed) {
+
preg_match('/at:\/\/(did:plc:[a-z0-9]+)\/app.bsky.feed.post\/([a-z0-9]+)/', $feed->post, $uriComponents);
+
$did = $uriComponents[1];
+
$rkey = $uriComponents[2];
+
return $this->getPost($did, $rkey, true);
+
}, $body->feed);
+
return $body;
+
}
+
/* AUTH */
/* HELPERS */
···
$ret = $this->getConstellationLinkData($post, "app.bsky.feed.post", ".reply.root.uri");
if ($ret) {
return array_map(function ($rec) {
-
$slingshotRecord = $this->getSlingshotData($rec->did, $rec->collection, $rec->rkey, true);
+
$slingshotRecord = $this->getSlingshotData($rec->did, $rec->collection, $rec->rkey, ['reverse' => true]);
return $this->sanitizePost($slingshotRecord, true);
}, $ret->linking_records);
}
return [];
}
-
function getUserPosts(string $did, $auth = false, $cursor = null, $newer = false):array|bool {
+
function getUserPosts(string $did, ?string $cursor = null, bool $auth = false, bool $newer = false): array|bool {
$userData = $this->getUserInfo($did, 'did');
$postData = $this->getPdsData($userData->pds, 'com.atproto.repo.listRecords', [
'repo' => $did,
···
return $feedData;
}
-
function getFeed(string $atUri, ?string $cursor = null, ?string $userAuth = null, ?bool $newer = false, int $limit = 15):array|bool {
+
function getFeed(string $atUri, ?string $cursor = null, ?string $userAuth = null, ?bool $newer = false, int $limit = 15):object|bool {
+
preg_match('/^at:\/\/(did:plc:[a-z0-9\.]+)\/app.bsky.feed.generator\/([a-z0-9]+)$/', $atUri, $uriComponents);
+
$did = $uriComponents[1];
+
$rkey = $uriComponents[2];
+
$userInfo = $this->getUserInfo($did, 'did');
+
+
if (!$userInfo) return false;
+
+
$feedInfo = $this->getPdsData($userInfo->pds, 'com.atproto.repo.getRecord', [
+
'repo' => $did,
+
'collection' => 'app.bsky.feed.generator',
+
'rkey' => $rkey
+
]);
+
+
if (!$feedInfo) return false;
+
if ($userAuth) {
// do something. i don't care rn tho
···
return false;
}
-
$feedData = $this->getPublicApiData('app.bsky.feed.getFeed', [
-
'feed' => $atUri,
-
'limit' => $limit,
-
'cursor' => $cursor
-
]);
+
$feedData = $this->getFeedSkeleton($feedInfo->value->did, $atUri, $cursor);
if ($feedData) {
-
$newCursor = $feedData->cursor;
-
$feed = $feedData->feed;
-
-
return $this->sanitizePublicApiPosts($feed, $newCursor, $cursor, $newer);
+
return $feedData;
}
return false;
···
'pds' => $authorInfo->pds,
'postId' => $rkey,
'postLink' => '/u/'.$authorInfo->handle.'/'.$rkey,
-
'content' => $this->applyFacets($post->value->text, $facets),
+
'content' => property_exists($post->value, 'text') ? $this->applyFacets($post->value->text, $facets) : '',
'replyCount' => $this->getReplies($post->uri),
'repostCount' => $this->getReposts($post->uri),
'likeCount' => $this->getLikes($post->uri),
+3 -1
templates/feed.latte
···
{block content}
{include '_partials/feedHeader.latte', displayName: $feedName, description: $feedDescription, avatar: $feedAvatar, creatorDisplay: $feedAuthorName, creatorHandle: $feedAuthorHandle, creatorPds: $feedAuthorPds, creatorDid: $feedAuthorDid, feedAtUri: $feedAtUri}
-
{include '_partials/feedPosts.latte', posts: $posts}
+
<div class="postList" data-uri="{$feedAtUri}" data-cursor="{$cursor}">
+
{include '_partials/feedPosts.latte', posts: $posts}
+
</div>
{/block}
+3 -1
templates/home.latte
···
{layout 'layout.latte'}
{block content}
-
{include '_partials/feedPosts.latte', posts: $posts}
+
<div class="postsList" data-uri="{$feedAtUri}" data-cursor="{$cursor}">
+
{include '_partials/feedPosts.latte', posts: $posts}
+
</div>
{/block}