FinalReleaseChanges (#49)

- Loads of bug fixes.
- CSS improvements.
- Tags added.
- Optimized some things.
- Some other oddities? I don't know :3
- Happiness increased by 2%.
This commit is contained in:
CatAClock 2025-06-05 00:25:39 +00:00
parent f03e73132a
commit 8c69eaa88a
16 changed files with 393 additions and 183 deletions

View file

@ -1,3 +1,7 @@
html {
background: linear-gradient(to right, #00FFFF, #dcdcdc, #dcdcdc, #dcdcdc, #dcdcdc, #dcdcdc, #00FFFF);
}
.Button {
margin-top: 30px;
@ -13,14 +17,46 @@ body {
text-align: center;
}
section {
display: flex;
flex-wrap: wrap;
}
div:not(.Posts) {
margin-left: auto;
margin-right: auto;
margin-bottom: auto;
border-style: solid;
border-width: 2px 0;
width: 49%;
}
#stick {
position: sticky;
top: 0;
}
img {
width: 20vh;
height: 20vh;
}
footer {
display: flex;
justify-content: center;
height: 5vh;
height: 20vh;
}
/* Mastodon things */
.Trees {
display: flex;
justify-content: center;
flex-wrap: wrap;
}
.Entry {
border-left: 2px solid;
border-right: 1px dashed;

View file

@ -15,7 +15,7 @@ body {
.Embed {
border-style: solid;
border-radius: 1%;
border-radius: 2%;
background-color: yellow;
}
@ -40,6 +40,10 @@ body {
background-color: #050505;
}
.Favorite, .Boost, .Reply {
margin: auto;
}
.Button {
margin-top: 30px;

View file

@ -221,9 +221,9 @@ html {
.Username {
text-align: center;
font-weight: bold;
font-size: 2ch;
font-size: min(2vw, 2ch);
margin-top: -5px;
margin-top: max(-5px, -0.5vw);
}
.PostContent {
@ -247,6 +247,11 @@ html {
.Time {
font-size: 4ch;
text-decoration: none;
}
.Time:hover {
text-decoration: underline;
}
.MainFooter, .MainBlackedFooter {
@ -260,6 +265,8 @@ html {
border-width: 2px;
border-color: #00FFFF;
padding: auto;
height: 20vh;
}

View file

@ -1,6 +1,6 @@
/* Combination */
header, footer {
style="position: relative;
position: relative;
z-index: 1;
}

View file

@ -1,4 +1,5 @@
html {
background: linear-gradient(#FFFFFF, #dcdcdc, #2eff51);
overflow-x: hidden;
overflow-y: hidden;
}
@ -14,6 +15,43 @@ body {
section {
height: 75vh;
display: flex;
flex-wrap: wrap;
justify-content: center;
text-align: center;
}
textarea, select, input, label {
resize: none;
border-style: solid;
border-width: 2px;
border-color: #00FFFF;
border-radius: 2% / 50%;
}
.Divide {
margin-top: auto;
margin-bottom: auto;
margin-left: 1%;
margin-right: 1%;
border-style: solid;
border-width: 4px;
border-radius: 10%;
padding: 1%;
background-color: white;
filter: drop-shadow(0px 20px 10px #00cf23);
transition: background-color 0.5s, filter 0.5s;
}
.Button:hover {
background-color: #dcdcdc;
filter: drop-shadow(0px -20px 10px #2eff51);
}
.SmallButton {
@ -30,5 +68,5 @@ footer {
display: flex;
justify-content: center;
height: 5vh;
height: 20vh;
}

View file

@ -28,9 +28,8 @@ body {
border-width: 6px;
border-color: #00FFFF;
padding: 8%;
padding: 8vh 6vw;
display: flex;
justify-content: center;
background: linear-gradient(#dcdcdc, #b4b4b4);
color: black;

View file

@ -15,18 +15,25 @@
<header>
<h1>Account</h1>
</header>
<div>
<h2 class="Account"></h2>
<img class="AccountImage" />
<p class="AccountDescription"></p>
<div style="display: flex; justify-content: center;">
<p class="Button Follow">Follow!</p>
<p class="Button Block">Block!</p>
<section>
<!-- Account information goes into this div. -->
<div id="stick">
<h2 class="Account"></h2>
<img class="AccountImage"/>
<p class="AccountDescription"></p>
<aside style="display: flex; justify-content: center;">
<p class="Button Follow">Follow!</p>
<p class="Button Block">Block!</p>
</aside>
</div>
<div class="Posts">
<p>Not implemented. Posts should populate here.</p>
<!-- Either other account info goes here, or posts go here. -->
<div>
<h2 class="Account"></h2>
<img class="AccountImage" hidden=true/>
<p class="AccountDescription"></p>
<div class="Posts"></div>
</div>
</div>
</section>
<footer>
<p class="Button" onclick="history.back();"><b>Back</b></p>
</footer>

View file

@ -35,7 +35,8 @@
</header>
<p class="PostText Regular"></p>
<div class="Images Regular"></div>
<div "display: flex;">
<!-- Interactables. -->
<div style="display: flex;">
<p class="Favorite">Favorite!</p>
<p class="Boost">Boost!</p>
<p class="Reply">Reply!</p>

View file

@ -16,19 +16,28 @@
<h1>Post</h1>
</header>
<section>
<textarea cols="50" rows="25" class="Text" placeholder="status here..."></textarea>
<div>
<!-- This the typing div :3 -->
<div class="Divide">
<textarea cols="50" rows="25" class="Text" placeholder="status here..."></textarea>
<div><textarea cols="50" rows="2" class="Warning" maxlength="100" placeholder="Content Warnings"></textarea></div>
<div><textarea cols="50" rows="1" class="Tags" maxlength="50" placeholder="Tags (seperate with ;)"></textarea></div>
</div>
<!-- This the "settings" div :3 -->
<div class="Divide">
<select class="PostVisibility">
<option value="Public">Public Post</option>
<option value="Quiet">Quiet Public Post</option>
<option value="Friend">Friends Only Post</option>
<option value="Private">Private Post</option>
</select>
<input type="checkbox" class="PostYoutube" />
<label>Post to Youtube</label>
<div></div>
<input type="checkbox" class="PostYoutube" />
<label>Post to Youtube</label>
</div>
</div>
<div class="Divide Button">
<p>POST!</p>
</div>
<textarea cols="50" rows="1" class="Warning" placeholder="Content Warnings"></textarea>
<p class="Button">POST!</p>
</section>
<footer>
<p class="SmallButton" onclick="history.back();"><b>Back</b></p>

View file

@ -15,31 +15,29 @@
<header>
<h1>Setting</h1>
</header>
<div style="display: flex; justify-content: center; height: 75vh;">
<div style="display: flex; flex-wrap: wrap; justify-content: center; height: 75vh;">
<!-- Toggling things. -->
<div class="Button">
<p class="Remote">Toggle Remote</p>
</div>
<!-- Mastodon things. -->
<div class="Button">
<!-- Sorry people! -->
<!-- Mastodon things. -->
<div>
<input type="text" class="WebInput Mastodon" placeholder="Website (mastodon.social)"/>
<p class="Login Mastodon"><em>Login to Mastodon</em></p>
<p class="Logout Mastodon Hidden"><em>Logout of Mastodon</em></p>
</div>
<!-- Bluesky things. -->
<div>
<input type="text" class="WebInput Bluesky" placeholder="Website (bsky.social)"/>
<p class="Login Bluesky"><em>Login to Bluesky</em></p>
<p class="Logout Bluesky Hidden"><em>Logout of Bluesky</em></p>
</div>
<!-- Youtube things. -->
<div>
<input type="text" class="WebInput Youtube" placeholder="Youtube API Key"/>
<input type="text" class="WebInput Youtube" placeholder="Youtube Channel Handle"/>
<p class="Login Youtube"><em>Login to Youtube</em></p>
<p class="Logout Youtube Hidden"><em>Logout of Youtube</em></p>
</div>
<input type="text" class="WebInput Mastodon" placeholder="Website (mastodon.social)"/>
<p class="Login Mastodon"><em>Login to Mastodon</em></p>
<p class="Logout Mastodon Hidden"><em>Logout of Mastodon</em></p>
</div>
<!-- Bluesky things. -->
<div class="Button">
<input type="text" class="WebInput Bluesky" placeholder="Website (bsky.social)"/>
<p class="Login Bluesky"><em>Login to Bluesky</em></p>
<p class="Logout Bluesky Hidden"><em>Logout of Bluesky</em></p>
</div>
<!-- Youtube things. -->
<div class="Button">
<input type="text" class="WebInput Youtube" placeholder="Youtube API Key"/>
<input type="text" class="WebInput Youtube" placeholder="Youtube Channel Handle"/>
<p class="Login Youtube"><em>Login to Youtube</em></p>
<p class="Logout Youtube Hidden"><em>Logout of Youtube</em></p>
</div>
</div>
<footer>

View file

@ -65,6 +65,23 @@ export async function GetProfile(DID) {
return body;
}
export async function GetProfileFeed(DID) {
if (Token == null) {
return "";
}
let FetchThing = localStorage.getItem(Variables.BlueskyPDS) + "/xrpc/app.bsky.feed.getAuthorFeed?actor=" + DID;
let DPoP = await ClientDPoPPDS("GET", FetchThing);
let request = fetch(FetchThing, { method: "GET", headers: {"Authorization": "DPoP " + Token, "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 GetProfileFeed(DID);
}
return body;
}
// This gets the post. If there are multiple URIs, they must be within an array.
export async function GetPosts(URIs) {
if (Token == null) {
@ -115,7 +132,7 @@ export async function GetRecord(Repo, Collection, RKey) {
// Creators
// This creates a post. Requires the DID of the account and the Text for the record.
export async function CreatePost(DID, Text, ContentWarning = undefined) {
export async function CreatePost(DID, Text, ContentWarning = undefined, Tags = [], ReplyID = undefined, RootID = undefined) {
if (Token == null) {
return "";
}
@ -124,34 +141,18 @@ export async function CreatePost(DID, Text, ContentWarning = undefined) {
"text": Text,
"createdAt": new Date(Date.now()).toISOString()
};
// Content warning stuff.
if (ContentWarning != undefined) {
Record.labels = {
"$type": "com.atproto.label.defs#selfLabels",
"values": [{
"val": ContentWarning
}]
}
};
}
let body = await CreateRecord(DID, "app.bsky.feed.post", Record, undefined);
if (body.hasOwnProperty("error") && body.error == "InvalidRequest") {
let matches = body.message.match(/(\d+)/);
Record.text = Text.slice(0, matches[0] - 1);
body = await CreateRecord(DID, "app.bsky.feed.post", Record, undefined);
await CreateReplyPost(DID, Text.slice(matches[0] - 1, Text.length - 1), body, body);
}
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, ContentWarning = undefined) {
if (Token == null) {
return "";
}
let Record = {
"$type": "app.bsky.feed.post",
"text": Text,
"createdAt": new Date(Date.now()).toISOString(),
"reply": {
// ReplyID and RootID simultaniously.
if (ReplyID != undefined && RootID != undefined) {
Record.reply = {
"parent": {
"uri": ReplyID.uri,
"cid": ReplyID.cid
@ -160,22 +161,22 @@ export async function CreateReplyPost(DID, Text, ReplyID, RootID, ContentWarning
"uri": RootID.uri,
"cid": RootID.cid
}
}
};
if (ContentWarning != undefined) {
Record.labels = {
"$type": "com.atproto.label.defs#selfLabels",
"values": [{
"val": ContentWarning
}]
}
};
}
// Tags
if (Tags.length != 0) {
Record.tags = Tags;
}
let body = await CreateRecord(DID, "app.bsky.feed.post", Record, undefined);
if (body.hasOwnProperty("error") && body.error == "InvalidRequest") {
let matches = body.message.match(/(\d+)/);
Record.text = Text.slice(0, matches[0] - 1);
body = await CreateRecord(DID, "app.bsky.feed.post", Record, undefined);
await CreateReplyPost(DID, Text.slice(matches[0] - 1, Text.length - 1), body, RootID);
if (ReplyID == undefined && RootID == undefined) {
await CreatePost(DID, Text.slice(matches[0] - 1, Text.length), ContentWarning, Tags, body, body);
} else {
await CreatePost(DID, Text.slice(matches[0] - 1, Text.length), ContentWarning, Tags, body, RootID);
}
}
return body;
}
@ -402,8 +403,7 @@ export async function DeleteRecord(Repo, Collection, RKey) {
// 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"})
.then((response) => response.json());
return fetch("https://bsky.social/.well-known/oauth-authorization-server", {method: "GET"});
}
// Component 2/4
@ -525,7 +525,7 @@ export async function HandleAuthorization(Website) {
// Declare Variables
let KeyPair = await crypto.subtle.generateKey({name: "ECDSA", namedCurve: "P-256"}, true, ["sign", "verify"]);
let WellKnown = await GetPDSWellKnown();
let WellKnown = await GetPDSWellKnown().then((response) => response.json());
let State = crypto.randomUUID();
@ -551,7 +551,7 @@ export async function HandleAuthorization(Website) {
}
export async function GainTokens() {
let WellKnown = await GetPDSWellKnown();
let WellKnown = await GetPDSWellKnown().then((response) => response.json());
// Check to see if something's a miss...
if ((document.location.href.split("state=").length > 1 && document.location.href.split("iss=").length > 1 && document.location.href.split("code=").length > 1) && localStorage.getItem(Variables.BlueskyPKCEVerifier) != null && localStorage.getItem(Variables.BlueskyAccessToken) == null) {
@ -562,41 +562,41 @@ export async function GainTokens() {
let Var = await localStorage.getItem(Variables.BlueskyPKCEVerifier);
let Auth = await AuthRequest(WellKnown.token_endpoint, code, DPoP, Var);
// Save the tokens and be done
localStorage.setItem(Variables.BlueskyAccessToken, Auth.access_token);
localStorage.setItem(Variables.BlueskyRefreshToken, Auth.refresh_token);
await localStorage.setItem(Variables.BlueskyAccessToken, Auth.access_token);
await localStorage.setItem(Variables.BlueskyRefreshToken, Auth.refresh_token);
// That long string just gets the payload
// aud = PDS server we are communicating with; sub = user DID
localStorage.setItem(Variables.BlueskyPDS, "https://" + KJUR.jws.JWS.readSafeJSONString(b64utoutf8(localStorage.getItem(Variables.BlueskyAccessToken).split(".")[1])).aud.split(":")[2]);
localStorage.setItem(Variables.BlueskyDID, KJUR.jws.JWS.readSafeJSONString(b64utoutf8(localStorage.getItem(Variables.BlueskyAccessToken).split(".")[1])).sub);
await localStorage.setItem(Variables.BlueskyPDS, "https://" + KJUR.jws.JWS.readSafeJSONString(b64utoutf8(localStorage.getItem(Variables.BlueskyAccessToken).split(".")[1])).aud.split(":")[2]);
await localStorage.setItem(Variables.BlueskyDID, KJUR.jws.JWS.readSafeJSONString(b64utoutf8(localStorage.getItem(Variables.BlueskyAccessToken).split(".")[1])).sub);
}
}
// Refreshing tokens is an integral part of auth.
export async function RefreshTokens() {
let WellKnown = await GetPDSWellKnown();
let WellKnown = await GetPDSWellKnown().then((response) => response.json());
// Fake PAR request to get the nonce.
let State = crypto.randomUUID();
let PKCEverifier = await CreatePKCECodeVerifier();
let PKCEchallenge = await CreatePKCECodeChallenge(PKCEverifier);
let PAR = PARrequest(WellKnown.pushed_authorization_request_endpoint, State, PKCEchallenge);
let nonce = await PAR.then((response) => response.headers.get("dpop-nonce"));
// Save nonce
await localStorage.setItem(Variables.BlueskyNonce, nonce);
// Create varaibles, be aware of waits because of internet.
let DPoP = await ClientDPoPToken("POST", WellKnown.token_endpoint);
// Token refresh
let Var = await localStorage.getItem(Variables.BlueskyRefreshToken);
let Auth = ReauthRequest(WellKnown.token_endpoint, Var, DPoP);
let body = await Auth.then((response) => response.json());
let header = await Auth.then((response) => response.headers.get("dpop-nonce"));
if (body.hasOwnProperty("error_description") && body.error_description.includes("DPoP nonce mismatch")) {
localStorage.setItem(Variables.BlueskyNonce, header);
DPoP = await ClientDPoPToken("POST", WellKnown.token_endpoint);
body = await ReauthRequest(WellKnown.token_endpoint, Var, DPoP).then((response) => response.json());
}
let Auth = await ReauthRequest(WellKnown.token_endpoint, Var, DPoP).then((response) => response.json());
// Save the tokens and be done
localStorage.setItem(Variables.BlueskyAccessToken, body.access_token);
localStorage.setItem(Variables.BlueskyRefreshToken, body.refresh_token);
await localStorage.setItem(Variables.BlueskyAccessToken, Auth.access_token);
await localStorage.setItem(Variables.BlueskyRefreshToken, Auth.refresh_token);
}
async function HandleError(body, header) {
if (body.message.includes("DPoP nonce mismatch")) {
await localStorage.setItem(Variables.BlueskyNonce, header);
}
if (body.message.includes("claim timestamp check failed")) {
await localStorage.setItem(Variables.BlueskyNonce, header);
if (body.message != undefined && body.message.includes("claim timestamp check failed")) {
await RefreshTokens();
Token = localStorage.getItem(Variables.BlueskyAccessToken);
}
}

View file

@ -93,19 +93,59 @@ export async function GetStatus(ID) {
.then((response) => response.json());
}
// Get your own account by verifying your credentials.
export async function GetOwnAccount() {
if (Token == null || TokenType == null) {
return "";
}
return await fetch(localStorage.getItem(Variables.MastodonWebsite) + "/api/v1/accounts/verify_credentials", {method: "GET", headers: {"Authorization": TokenType + " " + Token}})
.then((response) => response.json());
}
// Get your own account blocks.
export async function GetOwnAccountBlocks() {
if (Token == null || TokenType == null) {
return "";
}
return await fetch(localStorage.getItem(Variables.MastodonWebsite) + "/api/v1/blocks/", {method: "GET", headers: {"Authorization": TokenType + " " + Token}})
.then((response) => response.json());
}
// Get another account's statuses.
export async function GetAccountStatuses(ID) {
if (Token == null || TokenType == null) {
return "";
}
return await fetch(localStorage.getItem(Variables.MastodonWebsite) + "/api/v1/accounts/" + ID + "/statuses", {method: "GET", headers: {"Authorization": TokenType + " " + Token}})
.then((response) => response.json());
}
// Get another account.
export async function GetAccount(ID) {
if (Token == null || TokenType == null) {
return "";
}
return await fetch(localStorage.getItem(Variables.MastodonWebsite) + "/api/v1/accounts/" + ID, {method: "GET", headers: {"Authorization": TokenType + " " + Token}})
.then((response) => response.json());
}
// Creators
// Make a status
export async function CreateStatus(Text, SpoilerText = undefined, Visibility = "public") {
export async function CreateStatus(Text, Visibility = "public", SpoilerText = undefined, ReplyID = undefined) {
if (Token == null || TokenType == null) {
return "";
}
// Stolen from StackOverflow.
Text = Text.replace(/(?:\r|\n|\r\n)/g, '<br>');
// Get the correct fetch body.
let FetchThing = localStorage.getItem(Variables.MastodonWebsite) + "/api/v1/statuses?status=" + Text + "&visibility=" + Visibility;
let FetchThing = localStorage.getItem(Variables.MastodonWebsite) + "/api/v1/statuses?status=" + encodeURIComponent(Text) + "&visibility=" + Visibility;
// Content warning stuff.
if (SpoilerText != undefined) {
FetchThing += "&spoiler_text=" + SpoilerText;
}
if (ReplyID != undefined) {
FetchThing += "&in_reply_to_id=" + ReplyID;
}
// Send the request.
let request = fetch(FetchThing, {method: "POST", headers: {"Authorization": TokenType + " " + Token}});
let body = await request.then((response) => response.json());
@ -114,45 +154,16 @@ export async function CreateStatus(Text, SpoilerText = undefined, Visibility = "
if (status == 422) {
let matches = body.error.match(/(\d+)/);
// Get the correct fetch body.
FetchThing = localStorage.getItem(Variables.MastodonWebsite) + "/api/v1/statuses?status=" + Text.slice(0, matches[0] - 1) + "&visibility=" + Visibility
FetchThing = localStorage.getItem(Variables.MastodonWebsite) + "/api/v1/statuses?status=" + encodeURIComponent(Text.slice(0, matches[0] - 1)) + "&visibility=" + Visibility
if (SpoilerText != undefined) {
FetchThing += "&spoiler_text=" + SpoilerText;
}
// Send the request.
request = fetch(FetchThing, {method: "POST", headers: {"Authorization": TokenType + " " + Token}});
request = fetch(enFetchThing, {method: "POST", headers: {"Authorization": TokenType + " " + Token}});
body = await request.then((response) => response.json());
await CreateReplyStatus(Text.slice(matches[0] - 1, SpoilerText, Text.length - 1), Visibility, body.id);
}
return body;
}
export async function CreateReplyStatus(Text, SpoilerText = undefined, Visibility = "public", ReplyID) {
if (Token == null || TokenType == null) {
return "";
}
// Stolen from StackOverflow
Text = Text.replace(/(?:\r|\n|\r\n)/g, '<br>');
// Get the correct fetch body.
let FetchThing = localStorage.getItem(Variables.MastodonWebsite) + "/api/v1/statuses?status=" + Text + "&visibility=" + Visibility + "&in_reply_to_id=" + ReplyID;
if (SpoilerText != undefined) {
FetchThing += "&spoiler_text=" + SpoilerText;
}
// Send the request.
let request = fetch(FetchThing, {method: "POST", headers: {"Authorization": TokenType + " " + Token}});
let body = await request.then((response) => response.json());
let status = await request.then((response) => response.status);
// This is in case you went over the characters.
if (status == 422) {
let matches = body.error.match(/(\d+)/);
// Get the correct fetch body.
FetchThing = localStorage.getItem(Variables.MastodonWebsite) + "/api/v1/statuses?status=" + Text.slice(0, matches[0] - 1) + "&visibility=" + Visibility + "&in_reply_to_id=" + ReplyID;
if (SpoilerText != undefined) {
FetchThing += "&spoiler_text=" + SpoilerText;
if (ReplyID != undefined) {
await CreateReplyStatus(Text.slice(matches[0] - 1, Text.length), Visibility, SpoilerText, body.id);
}
// Send the request.
request = fetch(FetchThing, {method: "POST", headers: {"Authorization": TokenType + " " + Token}});
body = await request.then((response) => response.json());
await CreateReplyStatus(Text.slice(matches[0] - 1, Text.length - 1), SpoilerText, Visibility, body.id);
}
return body;
}
@ -205,7 +216,7 @@ export async function CreateBlock(ID, IsBlocked) {
return "";
}
let request = localStorage.getItem(Variables.MastodonWebsite) + "/api/v1/accounts/" + ID;
if (IsReblogged == false) {
if (IsBlocked == false) {
request += "/block";
} else {
request += "/unblock";

View file

@ -10,15 +10,24 @@ let post = JSON.parse(localStorage.getItem("post"));
let AccountName = document.getElementsByClassName("Account")[0];
let AccountImage = document.getElementsByClassName("AccountImage")[0];
let AccountDescription = document.getElementsByClassName("AccountDescription")[0];
let AlternateAccountName = document.getElementsByClassName("Account")[1];
let AlternateAccountImage = document.getElementsByClassName("AccountImage")[1];
let AlternateAccountDescription = document.getElementsByClassName("AccountDescription")[1];
let FollowButton = document.getElementsByClassName("Follow")[0];
let BlockButton = document.getElementsByClassName("Block")[0];
let AccountPosts = document.getElementsByClassName("Posts")[0];
// Other vars.
let Following = false;
let Blocking = false;
// SPAWN
GetAccount();
// The interaction is given to another function for async.
FollowButton.onclick = (event) => {
FollowBoop();
}
@ -33,6 +42,7 @@ async function FollowBoop() {
SetFollow();
}
// Extra thing for the frontend to say "hey I am the follow meister".
function SetFollow() {
Following = !(Following);
if (Following == true) {
@ -42,6 +52,7 @@ function SetFollow() {
}
}
// The interaction is given to another function for async.
BlockButton.onclick = (event) => {
BlockBoop();
}
@ -56,6 +67,7 @@ async function BlockBoop() {
SetBlock();
}
// Extra thing for the frontend to say "hey I am the block meister".
function SetBlock() {
Blocking = !(Blocking);
if (Blocking == true) {
@ -74,20 +86,24 @@ async function GetAccount() {
Blocking = !(Relations[0].blocking);
SetFollow();
SetBlock();
// Check for a reblog.
if (post.reblog != null) {
post = post.reblog;
}
AccountName.innerHTML = post.account.username;
AccountDescription.innerHTML = post.account.note;
AccountImage.setAttribute("src", post.account.avatar);
// Build the fields
let Field = "<div style=\"display: flex; justify-content: center;\">";
let Field = "<aside class=\"Trees\">";
for (let i of post.account.fields) {
Field += "<b class=\"Entry\">" + i.name + "</b><em class=\"Exitry\">" + i.value + "</em>";
}
Field += "</div>";
Field += "</aside>";
AccountDescription.innerHTML += Field;
// Get their recent posts. Determine if they are cewl or not.
let Posts = await MastodonAPI.GetAccountStatuses(post.account.id);
for (let i of Posts) {
if (i.reblog != null) {
i = i.reblog;
}
AccountPosts.innerHTML += i.content + "<br/><br/>";
}
} else if (website == "Bluesky") {
// Set the relationship follows
let thing = await BlueskyAPI.GetRecord(localStorage.getItem(Variables.BlueskyDID), "app.bsky.graph.follow", post.post.author.did);
@ -108,5 +124,40 @@ async function GetAccount() {
AccountName.innerHTML = account.handle;
AccountDescription.innerHTML = account.description;
AccountImage.setAttribute("src", account.avatar);
// Get their recent posts. Determine if they are cewl or not.
let Posts = await BlueskyAPI.GetProfileFeed(account.did);
console.log(Posts);
for (let i of Posts.feed) {
AccountPosts.innerHTML += i.post.record.text + "<br/><br/>";
}
} else {
FollowButton.setAttribute("hidden", "");
BlockButton.setAttribute("hidden", "");
AlternateAccountImage.setAttribute("hidden", false);
// This is meant for the regular account. A big ol' you :3
let Token = localStorage.getItem(Variables.MastodonAccessToken);
if (Token != null) {
let MastoProfile = await MastodonAPI.GetOwnAccount();
AccountName.innerHTML = MastoProfile.username;
AccountDescription.innerHTML = MastoProfile.note;
AccountImage.setAttribute("src", MastoProfile.avatar);
// Build the fields
let Field = "<div style=\"display: flex; justify-content: center;\">";
for (let i of MastoProfile.fields) {
Field += "<b class=\"Entry\">" + i.name + "</b><em class=\"Exitry\">" + i.value + "</em>";
}
Field += "</div>";
AccountDescription.innerHTML += Field;
}
Token = localStorage.getItem(Variables.BlueskyAccessToken);
if (Token != null) {
let BlueProfile = await BlueskyAPI.GetProfile(localStorage.getItem(Variables.BlueskyDID));
AlternateAccountImage.setAttribute("width", "20%");
AlternateAccountImage.setAttribute("height", "20%");
AlternateAccountName.innerHTML = BlueProfile.handle;
AlternateAccountDescription.innerHTML = BlueProfile.description;
AlternateAccountImage.setAttribute("src", BlueProfile.avatar);
}
}
}

View file

@ -20,6 +20,9 @@ let GrandParentpost;
document.getElementsByClassName("Origin Regular")[0].innerHTML = website;
GetPost();
// Fixes a bug where the interpreter sucks ass.
let EmbedCounter = 0;
// Button stuff
Favorite.onclick = (event) => {
if (website == "Mastodon") {
@ -83,20 +86,19 @@ async function SetThreadPost(element, i) {
// Functions and things.
async function GetPost() {
if (website == "Mastodon") {
// Check for a reblog.
if (post.reblog != null) {
document.getElementsByClassName("Handle Regular")[0].innerHTML = post.account.username + " ( R: " + post.reblog + " )";
} else {
document.getElementsByClassName("Handle Regular")[0].innerHTML = post.account.username;
}
document.getElementsByClassName("PostText Regular")[0].innerHTML = post.content;
// Set the texts. It's opposite because "setting" causes it to switch.
post = await MastodonAPI.GetStatus(post.id);
FavoriteFlipper = !(post.favourited);
BoostFlipper = !(post.reblogged);
SetFavorite();
SetBoost();
// Check for a reblog.
if (post.reblog != null) {
document.getElementsByClassName("Handle Regular")[0].innerHTML = post.reblog.account.username + " ( R: " + post.account.username + " )";
post = post.reblog;
} else {
document.getElementsByClassName("Handle Regular")[0].innerHTML = post.account.username;
}
document.getElementsByClassName("PostText Regular")[0].innerHTML = post.content;
// Show the image if it exists.
if (post.media_attachments.length != 0) {
for (let i of post.media_attachments) {
@ -105,10 +107,10 @@ async function GetPost() {
}
// Now time to see if there are any parents
if (post.in_reply_to_id != null) {
let ParentPost = await MastodonReplylFunction("Parent", post.in_reply_to_id);
Parentpost = await MastodonReplylFunction("Parent", post.in_reply_to_id);
// Now time to see if there are any grandparents
if (ParentPost == undefined && ParentPost.in_reply_to_id != null) {
MastodonReplylFunction("GrandParent", ParentPost.in_reply_to_id);
if (Parentpost != undefined && Parentpost.in_reply_to_id != null) {
GrandParentpost = await MastodonReplylFunction("GrandParent", Parentpost.in_reply_to_id);
}
}
} else if (website == "Bluesky") {
@ -144,7 +146,7 @@ async function GetPost() {
Parentpost = await BlueskyReplyFunction("Parent", post.reply.parent, post.reply.parent.author);
// Now time to see if there are any grandparents.
if (Parentpost.value.hasOwnProperty("reply")) {
GrandParentpost = await BlueskyReplyFunction("Parent", Parentpost.value.reply.parent, post.reply.grandparentAuthor);
GrandParentpost = await BlueskyReplyFunction("GrandParent", Parentpost.value.reply.parent, post.reply.grandparentAuthor);
}
}
} else {
@ -213,8 +215,8 @@ async function ApplyMedia(Media, Element, Author = undefined) {
// Record and media embed; They are from seperate posts.
if (Media.$type == "app.bsky.embed.recordWithMedia") {
await BlueskyBlobEmbed(Media.media, Element, Author);
var Texty = await BlueskyAPI.GetPosts([Media.record.record.uri]);
var Text = BlueskyAPI.ApplyFacets(Texty.posts[0].record, Texty.posts[0].record.text);
let Texty = await BlueskyAPI.GetPosts([Media.record.record.uri]);
let Text = BlueskyAPI.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/>";
// To stop confusion: this is for the RECORD EMBED! It gets an image from the record embed and puts it there.
@ -223,8 +225,8 @@ async function ApplyMedia(Media, Element, Author = undefined) {
}
// Just a record on it's own.
} else if (Media.$type == "app.bsky.embed.record") {
var Texty = await BlueskyAPI.GetPosts([Media.record.uri]);
var Text = BlueskyAPI.ApplyFacets(Texty.posts[0].record, Texty.posts[0].record.text);
let Texty = await BlueskyAPI.GetPosts([Media.record.uri]);
let Text = BlueskyAPI.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" || Texty.posts[0].record.embed.$type == "app.bsky.embed.video") {
@ -234,6 +236,7 @@ async function ApplyMedia(Media, Element, Author = undefined) {
} else if (Media.$type == "app.bsky.embed.images" || Media.$type == "app.bsky.embed.video") {
await BlueskyBlobEmbed(Media, Element, Author);
}
EmbedCounter = 0;
}
}
@ -241,15 +244,22 @@ async function ApplyMedia(Media, Element, Author = undefined) {
async function BlueskyBlobEmbed(Blob, Element, Author) {
if (Blob.$type == "app.bsky.embed.images") {
for (let i of Blob.images) {
Element.innerHTML += "<img src=\"\" alt=\"\"/>";
var Blobby = await BlueskyAPI.GetBlob(Author, i.image.ref.$link);
var ObjectURL = URL.createObjectURL(Blobby);
Element.innerHTML += "<img src=" + ObjectURL + " alt=\"" + EscapeRegExp(i.alt) + "\"/>";
let TempElement = Element.getElementsByTagName("img")[EmbedCounter];
EmbedCounter += 1;
TempElement.setAttribute("src", ObjectURL);
TempElement.setAttribute("alt", EscapeRegExp(i.alt));
}
// Video embed.
} else if (Blob.$type == "app.bsky.embed.video") {
Element.innerHTML += "<video controls src=\"\">Loading...</video>";
Element = Element.getElementsByTagName("video")[0];
var Blobby = await BlueskyAPI.GetBlob(Author, Blob.video.ref.$link);
var ObjectURL = URL.createObjectURL(Blobby);
Element.innerHTML += "<video controls src=" + ObjectURL + "></video>";
Element.setAttribute("src", ObjectURL);
Element.innerHTML = "";
}
}

View file

@ -19,6 +19,7 @@ let SettingButton = document.getElementsByClassName("Setting")[0];
let MailButton = document.getElementsByClassName("Mail")[0];
let PostingButton = document.getElementsByClassName("Posting")[0];
let FeedButton = document.getElementsByClassName("Feed")[0];
let TimeAccountButton = document.getElementsByClassName("Time")[0];
// Sounds
const ButtonSound = new Audio("Audio/button-305770.mp3");
@ -205,9 +206,6 @@ async function PosterContainerUpdate(Direction) {
if (Mastodon == false) {
countergroup = countergroup + 1;
}
if (Bluesky == false) {
countergroup = countergroup + 1;
}
// This var is meant to stop "already seen" posts.
let BlueskyPostsDup = [];
WebsiteAPIType = [];
@ -218,23 +216,26 @@ async function PosterContainerUpdate(Direction) {
// Begin by having the website we are using get pushed to the expanded view array and clean out the HTML.
WebsiteAPIType.push("Mastodon");
i.getElementsByClassName("PostContent")[0].innerHTML = "";
if (MastodonLoadedFeed[CurrentThing + counter] == undefined) {
if (MastodonLoadedFeed[CurrentThing + counter + 1] == undefined) {
let TempFeed;
if (Discover == true) {
if (localStorage.getItem("Remote") != null) {
TempFeed = await MastodonAPI.GetPublicTimeline(true, true, localStorage.getItem(Variables.MastodonWebsite), MastodonLoadedFeed[CurrentThing + counter - 1].id);
TempFeed = await MastodonAPI.GetPublicTimeline(true, true, localStorage.getItem(Variables.MastodonWebsite), MastodonLoadedFeed[CurrentThing + counter].id);
} else {
TempFeed = await MastodonAPI.GetPublicTimeline(true, false, localStorage.getItem(Variables.MastodonWebsite), MastodonLoadedFeed[CurrentThing + counter - 1].id);
TempFeed = await MastodonAPI.GetPublicTimeline(true, false, localStorage.getItem(Variables.MastodonWebsite), MastodonLoadedFeed[CurrentThing + counter].id);
}
} else {
TempFeed = await MastodonAPI.GetTimeline(MastodonLoadedFeed[CurrentThing + counter - 1].id);
TempFeed = await MastodonAPI.GetTimeline(MastodonLoadedFeed[CurrentThing + counter].id);
}
MastodonLoadedFeed = MastodonLoadedFeed.concat(TempFeed);
MastodonLoadedFeed = await MastodonLoadedFeed.concat(TempFeed);
}
// put the reblog into the regular post; Make changes as necessary.
if (MastodonLoadedFeed[CurrentThing + counter].reblog != null) {
i.getElementsByClassName("Username")[0].innerHTML = MastodonLoadedFeed[CurrentThing + counter].reblog.account.username + " ( R: " + MastodonLoadedFeed[CurrentThing + counter].account.username + " )";
// Fix a reblog issue.
let ReblogFix = MastodonLoadedFeed[CurrentThing + counter].account.username;
MastodonLoadedFeed[CurrentThing + counter] = MastodonLoadedFeed[CurrentThing + counter].reblog;
MastodonLoadedFeed[CurrentThing + counter].reblog = ReblogFix;
} else {
i.getElementsByClassName("Username")[0].innerHTML = MastodonLoadedFeed[CurrentThing + counter].account.username
}
@ -263,14 +264,14 @@ async function PosterContainerUpdate(Direction) {
// Begin by having the website we are using get pushed to the expanded view array and clean out the HTML.
WebsiteAPIType.push("Bluesky");
i.getElementsByClassName("PostContent")[0].innerHTML = "";
if (BlueskyLoadedFeed[CurrentThing + counter] == undefined) {
if (BlueskyLoadedFeed[CurrentThing + counter + 1] == undefined) {
let TempFeed;
if (Discover == true) {
BlueskyLoadedFeed = await BlueskyAPI.GetPublicTimeline(BlueskyLoadedFeed[CurrentThing + counter - 1].post.indexedAt).then((response) => response.feed);
BlueskyLoadedFeed = await BlueskyAPI.GetPublicTimeline(BlueskyLoadedFeed[CurrentThing + counter].post.indexedAt).then((response) => response.feed);
} else {
TempFeed = await BlueskyAPI.GetTimeline(BlueskyLoadedFeed[CurrentThing + counter - 1].post.indexedAt).then((response) => response.feed);
TempFeed = await BlueskyAPI.GetTimeline(BlueskyLoadedFeed[CurrentThing + counter].post.indexedAt).then((response) => response.feed);
}
BlueskyLoadedFeed = BlueskyLoadedFeed.concat(TempFeed);
BlueskyLoadedFeed = await BlueskyLoadedFeed.concat(TempFeed);
}
// check for "already seen" posts, then put it as a seen post.
BlueskyLoadedFeed = CheckForDups(BlueskyLoadedFeed, BlueskyPostsDup, counter);
@ -343,6 +344,12 @@ MailButton.onclick = (event) => {
window.location.href = "./HTML/mail.html";
}
// A quick way of opening the account.
// In theory, this should be in another "channel" or the mail area, but those areas are crowded.
TimeAccountButton.onclick = (event) => {
window.location.href = "./HTML/account.html";
}
// Open the posting area.
PostingButton.onclick = (event) => {
window.location.href = "./HTML/post.html";

View file

@ -9,6 +9,7 @@ let VisibilityDropdown = document.getElementsByClassName("PostVisibility")[0];
let InputArea = document.getElementsByClassName("Text")[0];
let YoutubePoser = document.getElementsByClassName("PostYoutube")[0];
let WarningInputArea = document.getElementsByClassName("Warning")[0];
let TagsInputArea = document.getElementsByClassName("Tags")[0];
// Clicking the beeg POST button.
PostButton.onclick = (event) => {
@ -32,6 +33,21 @@ async function Post() {
// Mastodon posting.
if (localStorage.getItem(Variables.MastodonAccessToken) != null) {
let TempVisible;
// Adding tags
let TempText = Text;
let Tags = TagsInputArea.value.split(";");
if (Tags.length > 1) {
TempText += "<br/><br/>";
}
for (let i of Tags) {
while (i[0] == " ") {
i = i.substring(1, i.length);
}
if (i[0] != "#" && i.length != 0) {
i = "#" + i;
}
TempText += i + " ";
}
switch(Visible) {
case "Public":
TempVisible = "public";
@ -48,24 +64,38 @@ async function Post() {
}
if (website == "Mastodon") {
if (WarningInputArea.value == "") {
await MastodonAPI.CreateReplyStatus(Text, undefined, TempVisible, JSON.parse(localStorage.getItem("post")).id);
await MastodonAPI.CreateStatus(TempText, TempVisible, undefined, JSON.parse(localStorage.getItem("post")).id);
} else {
await MastodonAPI.CreateReplyStatus(Text, WarningInputArea.value, TempVisible, JSON.parse(localStorage.getItem("post")).id);
await MastodonAPI.CreateStatus(TempText, TempVisible, WarningInputArea.value, JSON.parse(localStorage.getItem("post")).id);
}
InputArea.value = "";
WarningInputArea.value = "";
TagsInputArea.value = "";
return;
} else if (website == "All") {
if (WarningInputArea.value == "") {
await MastodonAPI.CreateStatus(Text, undefined, TempVisible);
await MastodonAPI.CreateStatus(TempText, TempVisible, undefined);
} else {
await MastodonAPI.CreateStatus(Text, WarningInputArea.value, TempVisible);
await MastodonAPI.CreateStatus(TempText, TempVisible, WarningInputArea.value);
}
}
}
// Bluesky posting.
if (localStorage.getItem(Variables.BlueskyAccessToken) != null) {
let TempVisible;
// Adding tags
let Tags = TagsInputArea.value.split(";");
let TagsTemp = [];
for (let i of Tags) {
while (i[0] == " ") {
i = i.substring(1, i.length);
}
if (i[0] != "#" && i.length != 0) {
i = "#" + i;
}
TagsTemp.push(i);
}
Tags = TagsTemp;
switch(Visible) {
case "Public":
TempVisible = undefined;
@ -84,28 +114,29 @@ async function Post() {
let Post;
if (JSON.parse(localStorage.getItem("post")).hasOwnProperty("reply")) {
if (WarningInputArea.value == "") {
Post = await BlueskyAPI.CreateReplyPost(localStorage.getItem(Variables.BlueskyDID), Text, JSON.parse(localStorage.getItem("post")).post, JSON.parse(localStorage.getItem("post")).reply.root);
Post = await BlueskyAPI.CreatePost(localStorage.getItem(Variables.BlueskyDID), Text, undefined, Tags, JSON.parse(localStorage.getItem("post")).post, JSON.parse(localStorage.getItem("post")).reply.root);
} else {
Post = await BlueskyAPI.CreateReplyPost(localStorage.getItem(Variables.BlueskyDID), Text, JSON.parse(localStorage.getItem("post")).post, JSON.parse(localStorage.getItem("post")).reply.root, WarningInputArea.value);
Post = await BlueskyAPI.CreatePost(localStorage.getItem(Variables.BlueskyDID), Text, WarningInputArea.value, Tags, JSON.parse(localStorage.getItem("post")).post, JSON.parse(localStorage.getItem("post")).reply.root);
}
await BlueskyAPI.CreateThreadGate(localStorage.getItem(Variables.BlueskyDID), Post.uri, TempVisible);
} else {
if (WarningInputArea.value == "") {
Post = await BlueskyAPI.CreateReplyPost(localStorage.getItem(Variables.BlueskyDID), Text, JSON.parse(localStorage.getItem("post")).post, JSON.parse(localStorage.getItem("post")).post);
Post = await BlueskyAPI.CreatePost(localStorage.getItem(Variables.BlueskyDID), Text, undefined, Tags, JSON.parse(localStorage.getItem("post")).post, JSON.parse(localStorage.getItem("post")).post);
} else {
Post = await BlueskyAPI.CreateReplyPost(localStorage.getItem(Variables.BlueskyDID), Text, JSON.parse(localStorage.getItem("post")).post, JSON.parse(localStorage.getItem("post")).post, WarningInputArea.value);
Post = await BlueskyAPI.CreatePost(localStorage.getItem(Variables.BlueskyDID), Text, WarningInputArea.value, Tags, JSON.parse(localStorage.getItem("post")).post, JSON.parse(localStorage.getItem("post")).post);
}
await BlueskyAPI.CreateThreadGate(localStorage.getItem(Variables.BlueskyDID), Post.uri, TempVisible);
}
InputArea.value = "";
WarningInputArea.value = "";
TagsInputArea.value = "";
return;
} else if (website == "All") {
let Post;
if (WarningInputArea.value == "") {
Post = await BlueskyAPI.CreatePost(localStorage.getItem(Variables.BlueskyDID), Text);
Post = await BlueskyAPI.CreatePost(localStorage.getItem(Variables.BlueskyDID), Text, undefined, Tags);
} else {
Post = await BlueskyAPI.CreatePost(localStorage.getItem(Variables.BlueskyDID), Text, WarningInputArea.value);
Post = await BlueskyAPI.CreatePost(localStorage.getItem(Variables.BlueskyDID), Text, WarningInputArea.value, Tags);
}
await BlueskyAPI.CreateThreadGate(localStorage.getItem(Variables.BlueskyDID), Post.uri, TempVisible);
}
@ -118,6 +149,7 @@ async function Post() {
// The input being cleared means that the posting happened.
InputArea.value = "";
WarningInputArea.value = "";
TagsInputArea.value = "";
}
// Check if you can interact with the textbox