tetratto/crates/app/src/public/js/me.js

581 lines
17 KiB
JavaScript
Raw Normal View History

2025-03-23 16:37:43 -04:00
(() => {
const self = reg_ns("me");
2025-04-03 22:36:58 -04:00
self.LOGIN_ACCOUNT_TOKENS = JSON.parse(
window.localStorage.getItem("atto:login_account_tokens") || "{}",
);
2025-03-23 16:37:43 -04:00
self.define("logout", async () => {
if (
!(await trigger("atto::confirm", [
"Are you sure you would like to do this?",
]))
) {
return;
}
fetch("/api/v1/auth/logout", {
method: "POST",
})
.then((res) => res.json())
.then((res) => {
trigger("atto::toast", [
2025-03-24 22:42:33 -04:00
res.ok ? "success" : "error",
2025-03-23 16:37:43 -04:00
res.message,
]);
if (res.ok) {
setTimeout(() => {
window.location.href = "/";
}, 150);
}
});
});
self.define("remove_post", async (_, id) => {
if (
!(await trigger("atto::confirm", [
"Are you sure you want to do this?",
]))
) {
return;
}
fetch(`/api/v1/posts/${id}`, {
method: "DELETE",
})
.then((res) => res.json())
.then((res) => {
trigger("atto::toast", [
res.ok ? "success" : "error",
res.message,
]);
});
});
self.define("react", async (_, element, asset, asset_type, is_like) => {
fetch("/api/v1/reactions", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
asset,
asset_type,
is_like,
}),
})
.then((res) => res.json())
.then((res) => {
trigger("atto::toast", [
res.ok ? "success" : "error",
res.message,
]);
if (res.ok) {
const like = element.parentElement.querySelector(
'[hook_element="reaction.like"]',
);
const dislike = element.parentElement.querySelector(
'[hook_element="reaction.dislike"]',
);
if (is_like) {
like.classList.add("green");
like.querySelector("svg").classList.add("filled");
dislike.classList.remove("red");
} else {
dislike.classList.add("red");
like.classList.remove("green");
like.querySelector("svg").classList.remove("filled");
}
}
});
});
2025-03-30 22:26:20 -04:00
self.define("remove_notification", (_, id) => {
fetch(`/api/v1/notifications/${id}`, {
method: "DELETE",
})
.then((res) => res.json())
.then((res) => {
trigger("atto::toast", [
res.ok ? "success" : "error",
res.message,
]);
});
});
2025-04-12 22:25:54 -04:00
self.define("update_notification_read_status", (_, id, read) => {
2025-03-30 22:26:20 -04:00
fetch(`/api/v1/notifications/${id}/read_status`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
read,
}),
})
.then((res) => res.json())
.then((res) => {
trigger("atto::toast", [
res.ok ? "success" : "error",
res.message,
]);
});
});
self.define("clear_notifs", async () => {
if (
!(await trigger("atto::confirm", [
"Are you sure you want to do this?",
]))
) {
return;
}
fetch("/api/v1/notifications/my", {
method: "DELETE",
})
.then((res) => res.json())
.then((res) => {
trigger("atto::toast", [
res.ok ? "success" : "error",
res.message,
]);
});
});
2025-04-10 18:16:52 -04:00
self.define("repost", (_, id, content, community) => {
fetch(`/api/v1/posts/${id}/repost`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
content,
community,
}),
})
.then((res) => res.json())
.then((res) => {
trigger("atto::toast", [
res.ok ? "success" : "error",
res.message,
]);
if (res.ok) {
setTimeout(() => {
window.location.href = `/post/${res.payload}`;
}, 100);
}
});
});
self.define("report", (_, asset, asset_type) => {
window.open(
`/mod_panel/file_report?asset=${asset}&asset_type=${asset_type}`,
);
});
2025-04-02 14:11:01 -04:00
self.define("seen", () => {
2025-04-04 21:42:08 -04:00
fetch("/api/v1/auth/user/me/seen", {
2025-04-02 14:11:01 -04:00
method: "POST",
})
.then((res) => res.json())
.then((res) => {
if (!res.ok) {
trigger("atto::toast", ["error", res.message]);
}
});
});
2025-04-03 22:36:58 -04:00
2025-04-13 12:15:14 -04:00
self.define("remove_question", async (_, id) => {
if (
!(await trigger("atto::confirm", [
"Are you sure you want to do this?",
]))
) {
return;
}
fetch(`/api/v1/questions/${id}`, {
method: "DELETE",
})
.then((res) => res.json())
.then((res) => {
trigger("atto::toast", [
res.ok ? "success" : "error",
res.message,
]);
});
});
2025-04-19 18:59:55 -04:00
self.define("ip_block_question", async (_, id) => {
if (
!(await trigger("atto::confirm", [
"Are you sure you want to do this?",
]))
) {
return;
}
fetch(`/api/v1/questions/${id}/block_ip`, {
method: "POST",
})
.then((res) => res.json())
.then((res) => {
trigger("atto::toast", [
res.ok ? "success" : "error",
res.message,
]);
});
});
2025-04-03 22:36:58 -04:00
// token switcher
self.define(
"set_login_account_tokens",
({ $ }, value) => {
$.LOGIN_ACCOUNT_TOKENS = value;
window.localStorage.setItem(
"atto:login_account_tokens",
JSON.stringify(value),
);
},
["object"],
);
self.define("login", ({ $ }, username) => {
const token = self.LOGIN_ACCOUNT_TOKENS[username];
if (!token) {
return;
}
window.location.href = `/api/v1/auth/token?token=${token}`;
});
self.define("render_token_picker", ({ $ }, element) => {
element.innerHTML = "";
for (const token of Object.entries($.LOGIN_ACCOUNT_TOKENS)) {
element.innerHTML += `<button class="quaternary w-full justify-start" onclick="trigger('me::login', ['${token[0]}'])">
<img
title="${token[0]}'s avatar"
2025-04-04 21:42:08 -04:00
src="/api/v1/auth/user/${token[0]}/avatar?selector_type=username"
2025-04-03 22:36:58 -04:00
alt="Avatar image"
class="avatar"
style="--size: 24px"
/>
<span>${token[0]}</span>
</button>`;
}
});
self.define("switch_account", () => {
document.getElementById("tokens_dialog").showModal();
});
2025-03-23 16:37:43 -04:00
})();
(() => {
const self = reg_ns("connections");
self.define("pkce_verifier", async (_, length) => {
let text = "";
const possible =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (let i = 0; i < length; i++) {
text += possible.charAt(
Math.floor(Math.random() * possible.length),
);
}
return text;
});
self.define("pkce_challenge", async (_, verifier) => {
const data = new TextEncoder().encode(verifier);
const digest = await window.crypto.subtle.digest("SHA-256", data);
return btoa(
String.fromCharCode.apply(null, [...new Uint8Array(digest)]),
)
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
});
self.define("delete", async (_, connection) => {
if (
!(await trigger("atto::confirm", [
"Are you sure you want to do this?",
]))
) {
return;
}
fetch(`/api/v1/auth/user/connections/${connection}`, {
method: "DELETE",
})
.then((res) => res.json())
.then((res) => {
trigger("atto::toast", [
res.ok ? "success" : "error",
res.message,
]);
if (res.ok) {
window.location.reload();
}
});
});
self.define("push_con_data", async (_, connection, data) => {
return await (
await fetch("/api/v1/auth/user/connections/_data", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
connection,
data,
}),
})
).json();
});
self.define("push_con_state", async (_, connection, data) => {
return await (
await fetch("/api/v1/auth/user/connections/_state", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
connection,
data,
}),
})
).json();
});
self.define("push_con_shown", async (_, connection, shown) => {
return await (
await fetch("/api/v1/auth/user/connections/_shown", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
connection,
shown,
}),
})
).json();
});
})();
(() => {
const self = reg_ns("spotify");
self.define("create_connection", (_, client_id) => {
fetch("/api/v1/auth/user/connections/spotify", {
method: "POST",
})
.then((res) => res.json())
.then(async (res) => {
// create challenge and store
const verifier = await trigger("connections::pkce_verifier", [
128,
]);
const challenge = await trigger("connections::pkce_challenge", [
verifier,
]);
await trigger("connections::push_con_data", [
"Spotify",
{
verifier,
challenge,
},
]);
// ...
const params = new URLSearchParams();
params.append("client_id", client_id);
params.append("response_type", "code");
params.append(
"redirect_uri",
`${window.location.origin}/auth/connections_link/Spotify`,
);
params.append(
"scope",
2025-04-26 16:49:32 -04:00
"user-read-recently-played user-read-playback-state user-read-currently-playing",
);
params.append("code_challenge_method", "S256");
params.append("code_challenge", challenge);
window.open(
`https://accounts.spotify.com/authorize?${params.toString()}`,
);
window.location.reload();
});
});
self.define("get_token", async (_, client_id, verifier, code) => {
const params = new URLSearchParams();
params.append("client_id", client_id);
params.append("grant_type", "authorization_code");
params.append("code", code);
params.append(
"redirect_uri",
`${window.location.origin}/auth/connections_link/Spotify`,
);
params.append("code_verifier", verifier);
const result = await fetch("https://accounts.spotify.com/api/token", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: params,
});
const { access_token, refresh_token, expires_in } = await result.json();
return [access_token, refresh_token, expires_in];
});
self.define("refresh_token", async (_, client_id, token) => {
const params = new URLSearchParams();
params.append("client_id", client_id);
params.append("grant_type", "refresh_token");
params.append("refresh_token", token);
const result = await fetch("https://accounts.spotify.com/api/token", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: params,
});
const { access_token, refresh_token, expires_in } = await result.json();
return [access_token, refresh_token, expires_in];
});
self.define("profile", async (_, token) => {
return await (
await fetch("https://api.spotify.com/v1/me", {
method: "GET",
headers: { Authorization: `Bearer ${token}` },
})
).json();
});
self.define("get_playing", async (_, token) => {
// <https://developer.spotify.com/documentation/web-api/reference/get-information-about-the-users-current-playback>
return await (
await fetch("https://api.spotify.com/v1/me/player", {
method: "GET",
headers: { Authorization: `Bearer ${token}` },
})
).json();
});
self.define("publish_playing", async (_, playing) => {
if (!playing.is_playing) {
return await trigger("connections::push_con_state", [
"Spotify",
{
external_urls: {},
data: {},
},
]);
}
if (playing.item.is_local) {
return;
}
if (
window.localStorage.getItem("atto:connections.spotify/id") ===
playing.item.id
) {
// item already pushed to connection, no need right now
return;
}
window.localStorage.setItem(
"atto:connections.spotify/id",
playing.item.id,
);
return await trigger("connections::push_con_state", [
"Spotify",
{
external_urls: {
track: playing.item.external_urls.spotify,
artist: playing.item.artists[0].external_urls.spotify,
album: playing.item.album.external_urls.spotify,
album_img: playing.item.album.images[0].url,
},
data: {
id: playing.item.id,
// track
track: playing.item.name,
artist: playing.item.artists[0].name,
album: playing.item.album.name,
// image
// img_w: playing.item.album.images[0].width.toString(),
// img_h: playing.item.album.images[0].width.toString(),
// times
timestamp: playing.timestamp.toString(),
progress_ms: playing.progress_ms.toString(),
duration_ms: playing.item.duration_ms.toString(),
},
},
]);
});
self.define("ms_time_text", (_, ms) => {
const minutes = Math.floor(ms / 60000);
const seconds = ((ms % 60000) / 1000).toFixed(0);
return `${minutes}:${(seconds < 10 ? "0" : "") + seconds}`;
});
self.define(
"timestamp",
({ $ }, updated_, progress_ms_, duration_ms_, display = "full") => {
const now = new Date().getTime();
const updated = Number.parseInt(updated_) + 8000;
2025-04-26 16:49:32 -04:00
let elapsed_since_update = now - updated;
if (elapsed_since_update < 0) {
elapsed_since_update = 0;
}
const progress_ms =
Number.parseInt(progress_ms_) + elapsed_since_update;
const duration_ms = Number.parseInt(duration_ms_);
if (progress_ms > duration_ms) {
// song is over
return "";
}
if (display === "full") {
return `${$.ms_time_text(progress_ms)}/${$.ms_time_text(duration_ms)} <span class="fade">(${Math.floor((progress_ms / duration_ms) * 100)}%)</span>`;
}
if (display === "left") {
return $.ms_time_text(progress_ms);
}
return $.ms_time_text(duration_ms);
},
);
})();