organization, commenting, and fixing facets to make multiple work

This commit is contained in:
CatAClock 2025-05-23 12:10:21 -07:00
parent 5c02cdcf5d
commit 456bd86379
4 changed files with 115 additions and 81 deletions

View file

@ -1,5 +1,8 @@
import * as Variables from "./Variables.js";
// Getters
// This gets the timeline. The cursor is a time in Z form.
export async function GetTimeline(Cursor) {
if (localStorage.getItem(Variables.BlueskyAccessToken) == null) {
console.log("No access token!");
@ -26,6 +29,7 @@ export async function GetTimeline(Cursor) {
return body;
}
// This gets the post. If there are multiple URIs, they must be within an array.
export async function GetPosts(URIs) {
if (localStorage.getItem(Variables.BlueskyAccessToken) == null) {
console.log("No access token!");
@ -52,6 +56,27 @@ export async function GetBlob(DID, CID) {
return body;
}
// Gets a record. The repo is the account DID, the collection is the type of record, and the key is the little bit at the end.
export async function GetRecord(Repo, Collection, RKey) {
let RequestBody = {
"repo": Repo,
"collection": Collection,
"rkey": RKey
}
let DPoP = await ClientDPoPPDS("GET", localStorage.getItem(Variables.BlueskyPDS) + "/xrpc/com.atproto.repo.getRecord?repo=" + Repo + "&collection=" + Collection + "&rkey=" + RKey);
let request = fetch(localStorage.getItem(Variables.BlueskyPDS) + "/xrpc/com.atproto.repo.getRecord?repo=" + Repo + "&collection=" + Collection + "&rkey=" + RKey, { method: "GET", headers: {"Content-Type": "application/json", "Authorization": "DPoP " + localStorage.getItem(Variables.BlueskyAccessToken), "DPoP": DPoP}});
let body = await request.then((response) => response.json());
let status = await request.then((response) => response.status);
let header = await request.then((response) => response.headers.get("dpop-nonce"));
if (status == 401) {
await HandleError(body, header);
body = await DeleteRecord(Repo, Collection, RKey);
}
return body;
}
// Creators
// This creates a post. Requires the DID of the account and the Text for the record.
export async function CreatePost(DID, Text) {
if (localStorage.getItem(Variables.BlueskyAccessToken) == null) {
console.log("No access token!");
@ -72,6 +97,7 @@ export async function CreatePost(DID, Text) {
return body;
}
// Creates a reply post. The RootID is always the first post, the ReplyID is the post you are replying to.
export async function CreateReplyPost(DID, Text, ReplyID, RootID) {
if (localStorage.getItem(Variables.BlueskyAccessToken) == null) {
console.log("No access token!");
@ -102,7 +128,8 @@ export async function CreateReplyPost(DID, Text, ReplyID, RootID) {
return body;
}
export async function SetThreadGate(DID, Post, VisibilitySettings) {
// Creates a Thread Gate for who can reply. Requires the account DID, a post, and a list of who is allowed.
export async function CreateThreadGate(DID, Post, VisibilitySettings) {
if (localStorage.getItem(Variables.BlueskyAccessToken) == null) {
console.log("No access token!");
return "";
@ -117,7 +144,8 @@ export async function SetThreadGate(DID, Post, VisibilitySettings) {
return body;
}
export async function SendLike(DID, RefURI, RefCID) {
// Create a like and send it to the server.
export async function CreateLike(DID, RefURI, RefCID) {
if (localStorage.getItem(Variables.BlueskyAccessToken) == null) {
console.log("No access token!");
return "";
@ -142,7 +170,8 @@ export async function SendLike(DID, RefURI, RefCID) {
return body;
}
export async function SendRepost(DID, RefURI, RefCID) {
// Create a repost and send it to the server.
export async function CreateRepost(DID, RefURI, RefCID) {
if (localStorage.getItem(Variables.BlueskyAccessToken) == null) {
console.log("No access token!");
return "";
@ -167,24 +196,7 @@ export async function SendRepost(DID, RefURI, RefCID) {
return body;
}
export async function GetRecord(Repo, Collection, RKey) {
let RequestBody = {
"repo": Repo,
"collection": Collection,
"rkey": RKey
}
let DPoP = await ClientDPoPPDS("GET", localStorage.getItem(Variables.BlueskyPDS) + "/xrpc/com.atproto.repo.getRecord?repo=" + Repo + "&collection=" + Collection + "&rkey=" + RKey);
let request = fetch(localStorage.getItem(Variables.BlueskyPDS) + "/xrpc/com.atproto.repo.getRecord?repo=" + Repo + "&collection=" + Collection + "&rkey=" + RKey, { method: "GET", headers: {"Content-Type": "application/json", "Authorization": "DPoP " + localStorage.getItem(Variables.BlueskyAccessToken), "DPoP": DPoP}});
let body = await request.then((response) => response.json());
let status = await request.then((response) => response.status);
let header = await request.then((response) => response.headers.get("dpop-nonce"));
if (status == 401) {
await HandleError(body, header);
body = await DeleteRecord(Repo, Collection, RKey);
}
return body;
}
// Creates a record. Universal way of making things.
export async function CreateRecord(Repo, Collection, Record, RKey) {
let RequestBody = {
"repo": Repo,
@ -206,6 +218,8 @@ export async function CreateRecord(Repo, Collection, Record, RKey) {
return body;
}
// Deleters
// Removes a record. Can be a like, a repost, a post, etc.
export async function DeleteRecord(Repo, Collection, RKey) {
let RequestBody = {
"repo": Repo,
@ -224,6 +238,7 @@ export async function DeleteRecord(Repo, Collection, RKey) {
return body;
}
// Things to get this to work.
// Component 1/4
export async function GetPDSWellKnown() {
return await fetch("https://bsky.social/.well-known/oauth-authorization-server", {method: "GET"})

View file

@ -2,6 +2,7 @@ import * as Variables from "./Variables.js";
export const Scopes = "read write follow push";
// Getters
// Gets the public timeline.
export async function GetPublicTimeline(Local = false, Remote = false, Website) {
// Variables can be found in `setting.js`
@ -77,6 +78,7 @@ export async function GetNotifications() {
.then((response) => response.json());
}
// A status is just a post. It gets it.
export async function GetStatus(ID) {
if (localStorage.getItem(Variables.MastodonAccessToken) == null) {
console.log("No access token!");
@ -88,6 +90,7 @@ export async function GetStatus(ID) {
.then((response) => response.json());
}
// Creators
// Make a status
export async function CreateStatus(Text, Visibility = "public") {
if (localStorage.getItem(Variables.MastodonAccessToken) == null) {
@ -133,7 +136,7 @@ export async function CreateReplyStatus(Text, Visibility = "public", ReplyID) {
return body;
}
export async function SendFavorite(ID, IsFavorited) {
export async function CreateFavorite(ID, IsFavorited) {
if (localStorage.getItem(Variables.MastodonAccessToken) == null) {
console.log("No access token!");
return "";
@ -148,7 +151,7 @@ export async function SendFavorite(ID, IsFavorited) {
}
}
export async function SendReblog(ID, IsReblogged) {
export async function CreateReblog(ID, IsReblogged) {
if (localStorage.getItem(Variables.MastodonAccessToken) == null) {
console.log("No access token!");
return "";
@ -163,6 +166,7 @@ export async function SendReblog(ID, IsReblogged) {
}
}
// Things to make this work
// The first step to using the app.
export async function HandleAuthentication(Website) {
// See if the user is smart enough to put https.

View file

@ -21,18 +21,18 @@ GetPost();
// Button stuff
Favorite.onclick = (event) => {
if (website == "Mastodon") {
MastodonAPI.SendFavorite(post.id, post.favourited);
MastodonAPI.CreateFavorite(post.id, post.favourited);
} else if (website == "Bluesky") {
BlueskyAPI.SendLike(localStorage.getItem(Variables.BlueskyDID), post.post.uri, post.post.cid);
BlueskyAPI.CreateLike(localStorage.getItem(Variables.BlueskyDID), post.post.uri, post.post.cid);
}
SetFavorite();
}
Boost.onclick = (event) => {
if (website == "Mastodon") {
MastodonAPI.SendReblog(post.id, post.reblogged);
MastodonAPI.CreateReblog(post.id, post.reblogged);
} else if (website == "Bluesky") {
BlueskyAPI.SendRepost(localStorage.getItem(Variables.BlueskyDID), post.post.uri, post.post.cid);
BlueskyAPI.CreateRepost(localStorage.getItem(Variables.BlueskyDID), post.post.uri, post.post.cid);
}
SetBoost();
}
@ -50,7 +50,7 @@ async function GetPost() {
document.getElementsByClassName("Handle Regular")[0].innerHTML = post.reblog.account.username + " ( R: " + post.account.username + " )";
if (post.reblog.media_attachments.length != 0) {
for (let i of post.reblog.media_attachments) {
await CreateMedia(i, document.getElementsByClassName("Images Regular")[0]);
await ApplyMedia(i, document.getElementsByClassName("Images Regular")[0]);
}
}
} else {
@ -59,7 +59,7 @@ async function GetPost() {
// Show the image if it exists.
if (post.media_attachments.length != 0) {
for (let i of post.media_attachments) {
await CreateMedia(i, document.getElementsByClassName("Images Regular")[0]);
await ApplyMedia(i, document.getElementsByClassName("Images Regular")[0]);
}
}
}
@ -72,38 +72,22 @@ async function GetPost() {
if (post.in_reply_to_id != null) {
var AnotherPost = await MastodonAPI.GetStatus(post.in_reply_to_id);
document.getElementsByClassName("Origin Parent")[0].innerHTML = website;
if (AnotherPost.reblog != null) {
document.getElementsByClassName("PostText Parent")[0].innerHTML = AnotherPost.reblog.content;
if (AnotherPost.reblog.media_attachments.length != 0) {
for (let i of AnotherPost.reblog.media_attachments) {
await CreateMedia(i, document.getElementsByClassName("Images Parent")[0]);
}
}
} else {
document.getElementsByClassName("Handle Parent")[0].innerHTML = AnotherPost.account.username;
document.getElementsByClassName("PostText Parent")[0].innerHTML = AnotherPost.content;
if (AnotherPost.media_attachments.length != 0) {
for (let i of AnotherPost.media_attachments) {
await CreateMedia(i, document.getElementsByClassName("Images Parent")[0]);
}
await ApplyMedia(i, document.getElementsByClassName("Images Parent")[0]);
}
}
// Now time to see if there are any grandparents
if (AnotherPost.in_reply_to_id != null) {
var AnotherAnotherPost = await MastodonAPI.GetStatus(AnotherPost.in_reply_to_id);
document.getElementsByClassName("Origin GrandParent")[0].innerHTML = website;
if (AnotherAnotherPost.reblog != null) {
document.getElementsByClassName("PostText GrandParent")[0].innerHTML = AnotherAnotherPost.reblog.content;
if (AnotherAnotherPost.reblog.media_attachments.length != 0) {
for (let i of AnotherAnotherPost.reblog.media_attachments) {
await CreateMedia(i, document.getElementsByClassName("Images GrandParent")[0]);
}
}
} else {
document.getElementsByClassName("Handle GrandParent")[0].innerHTML = AnotherAnotherPost.account.username;
document.getElementsByClassName("PostText GrandParent")[0].innerHTML = AnotherAnotherPost.content;
if (AnotherAnotherPost.media_attachments.length != 0) {
for (let i of AnotherAnotherPost.media_attachments) {
await CreateMedia(i, document.getElementsByClassName("Images GrandParent")[0]);
}
await ApplyMedia(i, document.getElementsByClassName("Images GrandParent")[0]);
}
}
}
@ -116,27 +100,13 @@ async function GetPost() {
document.getElementsByClassName("Handle Regular")[0].innerHTML = post.post.author.handle;
}
// Text. This will be modified later.
var Text = post.post.record.text;
// Check for facets. Facets are things that change what the text does or looks like.
if (post.post.record.hasOwnProperty("facets")) {
for (let i of post.post.record.facets) {
if (i.features[0].$type == "app.bsky.richtext.facet#link") {
var EmojiRegex = /\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?|\p{Emoji_Presentation}|\p{Emoji}\uFE0F/gu;
var EmojiObjects = Text.match(EmojiRegex);
var SubtractNumber = 0;
if (EmojiObjects != null) {
SubtractNumber = EmojiObjects.length * 2;
}
Text = Text.substring(0, i.index.byteStart - SubtractNumber) + "<a href='" + i.features[0].uri + "'>" + Text.substring(i.index.byteStart - SubtractNumber, i.index.byteEnd - SubtractNumber) + "</a>" + Text.substring(i.index.byteEnd - SubtractNumber, Text.length - 1);
}
}
}
var Text = ApplyFacets(post.post.record, post.post.record.text);
// Place the text.
Text = Text.replace(/\r?\n|\r/g, "<br/>");
document.getElementsByClassName("PostText Regular")[0].innerHTML = Text;
// Show the image if it exists.
if (post.post.record.hasOwnProperty("embed")) {
await CreateMedia(post.post.record, document.getElementsByClassName("Images Regular")[0], post.post.author.did);
await ApplyMedia(post.post.record, document.getElementsByClassName("Images Regular")[0], post.post.author.did);
}
// We don't need to update the post with new information. The repos are seperate.
// Set the texts. It's opposite because "setting" causes it to switch.
@ -155,11 +125,11 @@ async function GetPost() {
var AnotherPost = await BlueskyAPI.GetRecord(post.reply.parent.uri.split("/")[2], post.reply.parent.uri.split("/")[3], post.reply.parent.uri.split("/")[4]);
document.getElementsByClassName("Origin Parent")[0].innerHTML = website;
document.getElementsByClassName("Handle Parent")[0].innerHTML = post.reply.parent.author.handle;
Text = AnotherPost.value.text;
Text = ApplyFacets(AnotherPost.value, AnotherPost.value.text);
Text = Text.replace(/\r?\n|\r/g, "<br/>");
document.getElementsByClassName("PostText Parent")[0].innerHTML = Text;
if (AnotherPost.value.hasOwnProperty("embed")) {
await CreateMedia(AnotherPost.value, document.getElementsByClassName("Images Parent")[0], post.reply.parent.author.did);
await ApplyMedia(AnotherPost.value, document.getElementsByClassName("Images Parent")[0], post.reply.parent.author.did);
}
// Now time to see if there are any grandparents.
@ -167,11 +137,11 @@ async function GetPost() {
var AnotherAnotherPost = await BlueskyAPI.GetRecord(AnotherPost.value.reply.parent.uri.split("/")[2], AnotherPost.value.reply.parent.uri.split("/")[3], AnotherPost.value.reply.parent.uri.split("/")[4]);
document.getElementsByClassName("Origin GrandParent")[0].innerHTML = website;
document.getElementsByClassName("Handle GrandParent")[0].innerHTML = post.reply.grandparentAuthor.handle;
Text = AnotherAnotherPost.value.text;
Text = ApplyFacets(AnotherAnotherPost.value, AnotherAnotherPost.value.text);
Text = Text.replace(/\r?\n|\r/g, "<br/>");
document.getElementsByClassName("PostText GrandParent")[0].innerHTML = Text;
if (AnotherAnotherPost.value.hasOwnProperty("embed")) {
await CreateMedia(AnotherAnotherPost.value, document.getElementsByClassName("Images GrandParent")[0], post.reply.grandparentAuthor.did);
await ApplyMedia(AnotherAnotherPost.value, document.getElementsByClassName("Images GrandParent")[0], post.reply.grandparentAuthor.did);
}
}
}
@ -180,7 +150,9 @@ async function GetPost() {
}
}
async function CreateMedia(Media, Element, Author = undefined) {
// Applyers. Essentially applies a thing to an operation.
// Finds any associated media and applies it to the post.
async function ApplyMedia(Media, Element, Author = undefined) {
// Check to see if the image is on the same server.
if (website == "Mastodon") {
if (Media.type == "image") {
@ -199,8 +171,9 @@ async function CreateMedia(Media, Element, Author = undefined) {
} else if (website == "Bluesky") {
if (Media.embed.$type == "app.bsky.embed.record") {
var Texty = await BlueskyAPI.GetPosts([Media.embed.record.uri]);
console.log(Texty);
Element.innerHTML += "<p class='Embed'>" + Texty.posts[0].record.text + "</p><br/>";
var Text = ApplyFacets(Texty.posts[0].record, Texty.posts[0].record.text);
Text = Text.replace(/\r?\n|\r/g, "<br/>");
Element.innerHTML += "<p class='Embed'>" + Text + "</p><br/>";
if (Texty.posts[0].record.embed.$type == "app.bsky.embed.images") {
for (let i of Texty.posts[0].record.embed.images) {
var Blobby = await BlueskyAPI.GetBlob(Texty.posts[0].author.did, i.image.ref.$link);
@ -227,6 +200,48 @@ async function CreateMedia(Media, Element, Author = undefined) {
}
}
// Applies the necessary facets to the text.
function ApplyFacets(record, text) {
var StringArray = [];
var SplitAreas = [0];
var Hrefs = [];
var TempText = "";
if (record.hasOwnProperty("facets")) {
// First, append split areas.
for (let i of record.facets) {
if (i.features[0].$type == "app.bsky.richtext.facet#link") {
SplitAreas.push(i.index.byteStart);
SplitAreas.push(i.index.byteEnd);
Hrefs.push(i.features[0].uri);
Hrefs.push("");
}
}
// Last minute append.
SplitAreas.push(text.length);
// Remove emoji regex
var EmojiRegex = /\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?|\p{Emoji_Presentation}|\p{Emoji}\uFE0F/gu;
var EmojiObjects = TempText.match(EmojiRegex);
var SubtractNumber = 0;
if (EmojiObjects != null) {
SubtractNumber = EmojiObjects.length * 2;
}
// Now we split the string
for (let i = 1; i < SplitAreas.length; i++) {
StringArray.push(text.slice(SplitAreas[i - 1] - SubtractNumber, SplitAreas[i] - SubtractNumber));
}
// Finally, we append the string with <a>
for (let i = 0; i < StringArray.length; i += 2) {
TempText += StringArray[i] + "<a href='" + Hrefs[i] + "'>" + StringArray[i + 1] + "</a>";
}
// Last minute append.
TempText += StringArray[StringArray.length - 1];
return TempText;
} else {
return text;
}
}
// Setters
function SetFavorite() {
FavoriteFlipper = !(FavoriteFlipper);
if (FavoriteFlipper == false) {

View file

@ -72,16 +72,16 @@ async function Post() {
if (website == "Bluesky") {
if (JSON.parse(localStorage.getItem("post")).hasOwnProperty("reply")) {
let Post = await BlueskyAPI.CreateReplyPost(localStorage.getItem(Variables.BlueskyDID), Text, JSON.parse(localStorage.getItem("post")).post, JSON.parse(localStorage.getItem("post")).reply.root);
await BlueskyAPI.SetThreadGate(localStorage.getItem(Variables.BlueskyDID), Post.uri, TempVisible);
await BlueskyAPI.CreateThreadGate(localStorage.getItem(Variables.BlueskyDID), Post.uri, TempVisible);
return;
} else {
let Post = await BlueskyAPI.CreateReplyPost(localStorage.getItem(Variables.BlueskyDID), Text, JSON.parse(localStorage.getItem("post")).post, JSON.parse(localStorage.getItem("post")).post);
await BlueskyAPI.SetThreadGate(localStorage.getItem(Variables.BlueskyDID), Post.uri, TempVisible);
await BlueskyAPI.CreateThreadGate(localStorage.getItem(Variables.BlueskyDID), Post.uri, TempVisible);
return;
}
} else if (website == "All") {
let Post = await BlueskyAPI.CreatePost(localStorage.getItem(Variables.BlueskyDID), Text);
await BlueskyAPI.SetThreadGate(localStorage.getItem(Variables.BlueskyDID), Post.uri, TempVisible);
await BlueskyAPI.CreateThreadGate(localStorage.getItem(Variables.BlueskyDID), Post.uri, TempVisible);
}
}
// Youtube posting.