Compare commits

..

1 commit

Author SHA1 Message Date
2b4d8bbe1c Webfinger draft 2024-03-18 21:21:04 -05:00
4 changed files with 145 additions and 538 deletions

View file

@ -18,12 +18,11 @@
<section> <section>
<h1>Fediverse Madness</h1> <h1>Fediverse Madness</h1>
<p>Competitive bracket-based comparisons of Fediverse users.</p> <p>Competitive bracket-based comparisons of Fediverse users.</p>
<p>Requires JavaScript.</p>
</section> </section>
<section id="accessibility" hidden></section> <section id="accessibility" hidden></section>
</header> </header>
<main> <main>
<section id="whoami" hidden> <section id="whoami">
<h3>Who are you?</h3> <h3>Who are you?</h3>
<span class="userinputbox"> <span class="userinputbox">
<span>@</span> <span>@</span>
@ -35,7 +34,6 @@
<hr /> <hr />
<button id="submitwhoami_followers">Play (Followers)</button> <button id="submitwhoami_followers">Play (Followers)</button>
<button id="submitwhoami_following">Play (Following)</button> <button id="submitwhoami_following">Play (Following)</button>
<button id="submitwhoami_manual">Play (Manual)</button>
<p id="errorwhoami"></p> <p id="errorwhoami"></p>
</section> </section>
<section id="followers" hidden> <section id="followers" hidden>
@ -55,11 +53,7 @@
<button id="selectrandomfollowers">Select random</button> <button id="selectrandomfollowers">Select random</button>
<button id="deselectrandomfollowers">Deselect random</button> <button id="deselectrandomfollowers">Deselect random</button>
</div> </div>
<div> <button id="morefollowers">Load more</button>
<button id="morefollowers">Load more</button>
<button id="savehandlesfollowers">Save selection</button>
<button id="loadhandlesfollowers">Load selection</button>
</div>
<button id="submitfollowers" disabled>Done</button> <button id="submitfollowers" disabled>Done</button>
</section> </section>
<section id="following" hidden> <section id="following" hidden>
@ -79,41 +73,9 @@
<button id="selectrandomfollowing">Select random</button> <button id="selectrandomfollowing">Select random</button>
<button id="deselectrandomfollowing">Deselect random</button> <button id="deselectrandomfollowing">Deselect random</button>
</div> </div>
<div> <button id="morefollowing">Load more</button>
<button id="morefollowing">Load more</button>
<button id="savehandlesfollowing">Save selection</button>
<button id="loadhandlesfollowing">Load selection</button>
</div>
<button id="submitfollowing" disabled>Done</button> <button id="submitfollowing" disabled>Done</button>
</section> </section>
<section id="manual" hidden>
<h3>Manual list</h3>
<p id="loadingmanual"></p>
<table id="listmanual" hidden>
<tr id="listmanualvalues">
<th>S.</th>
<th>Av.</th>
<th>User</th>
<th>Handle</th>
</tr>
</table>
<div>
<button id="selectallmanual">Select all</button>
<button id="selectnomanual">Select none</button>
<button id="selectrandommanual">Select random</button>
<button id="deselectrandommanual">Deselect random</button>
</div>
<div>
<span class="userinputbox">
<span>@</span>
<input type="text" name="user" id="manualuser" class="userinstance" />
<span>@</span>
<input type="text" name="instance" id="manualinstance" class="userinstance" />
</span>
<button id="moremanual">Fetch</button>
</div>
<button id="submitmanual" disabled>Done</button>
</section>
<section id="game" hidden> <section id="game" hidden>
<h3>Fight!</h3> <h3>Fight!</h3>
<p id="gameBracketLevel">Bracket Level ...</p> <p id="gameBracketLevel">Bracket Level ...</p>
@ -124,9 +86,9 @@
<div> <div>
<h3 id="gameUserOneName"></h3> <h3 id="gameUserOneName"></h3>
<p id="gameUserOneId"></p> <p id="gameUserOneId"></p>
<!-- <p id="gameUserOneNote"></p> <p id="gameUserOneNote"></p>
<table id="gameUserOneFields"> <table id="gameUserOneFields">
</table> --> </table>
</div> </div>
</div> </div>
<div class="gridUser" id="gameUserTwo"> <div class="gridUser" id="gameUserTwo">
@ -134,9 +96,9 @@
<div> <div>
<h3 id="gameUserTwoName"></h3> <h3 id="gameUserTwoName"></h3>
<p id="gameUserTwoId"></p> <p id="gameUserTwoId"></p>
<!-- <p id="gameUserTwoNote"></p> <p id="gameUserTwoNote"></p>
<table id="gameUserTwoFields"> <table id="gameUserTwoFields">
</table> --> </table>
</div> </div>
</div> </div>
</div> </div>
@ -152,9 +114,9 @@
<div> <div>
<h3 id="winnerUserName"></h3> <h3 id="winnerUserName"></h3>
<p id="winnerUserId"></p> <p id="winnerUserId"></p>
<!-- <p id="winnerUserNote"></p> <p id="winnerUserNote"></p>
<table id="winnerUserFields"> <table id="winnerUserFields">
</table> --> </table>
</div> </div>
</div> </div>
<h3>Bracket</h3> <h3>Bracket</h3>
@ -167,7 +129,10 @@
<section id="accessibility" hidden></section> <section id="accessibility" hidden></section>
<script src="/scripts/accessibility.js"></script> <script src="/scripts/accessibility.js"></script>
<script> <script>
let game = null; let game = {
FOLLOWERS_LINK: "",
FOLLOWING_LINK: ""
};
</script> </script>
<script src="./script.js"></script> <script src="./script.js"></script>
<script src="./scripts/game.js"></script> <script src="./scripts/game.js"></script>

View file

@ -10,16 +10,15 @@ function escapeHtml(unsafe)
.replace(/<\/?object\/?>/g, ""); .replace(/<\/?object\/?>/g, "");
} }
const n=x=>null; const n = ()=>null;
const el_id_user = document.querySelector("#user"); const el_id_user = document.querySelector("#user");
const el_id_instance = document.querySelector("#instance"); const el_id_instance = document.querySelector("#instance");
const el_id_whoami = document.querySelector("#whoami"); const el_id_whoami = document.querySelector("#whoami");
const el_id_submitwhoamiFollowers = document.querySelector("#submitwhoami_followers"); const el_id_submitwhoamiFollowers = document.querySelector("#submitwhoami_followers");
const el_id_submitwhoamiFollowing = document.querySelector("#submitwhoami_following"); const el_id_submitwhoamiFollowing = document.querySelector("#submitwhoami_following");
const el_id_submitwhoamiManual = document.querySelector("#submitwhoami_manual");
const el_id_errorwhoami = document.querySelector("#errorwhoami"); const el_id_errorwhoami = document.querySelector("#errorwhoami");
let gamemodeFollowers = ""; let gamemodeFollowers;
el_id_user.addEventListener("input", (e) => { el_id_user.addEventListener("input", (e) => {
console.log(e); console.log(e);
@ -38,79 +37,53 @@ async function verify() {
el_id_instance.value = instance; el_id_instance.value = instance;
if (username.length < 1) return null; if (username.length < 1) return null;
if (instance.length < 1) return null; if (instance.length < 1) return null;
const webf_req = await fetch(`https://${instance}/.well-known/webfinger?resource=acct:${username}@${instance}`)
// webfinger domain delegation .catch(n); // who cares
const domain = await fetch(`https://${instance}/.well-known/webfinger?resource=acct:${username}@${instance}`).catch(n); if (webf_req == null) return null;
if (domain == null || !domain.ok) return null; const webf_json = await webf_req.json();
const domain_json = (await domain.json()); const user_req = await fetch(webf_json.links.find(x => x.type == "application/activity+json").href, {
const domain_url = new URL(domain_json.links.find(x=>x.type=="application/activity+json").href).hostname; headers: {"Accept": "application/activity+json"}
})
const user_req = await fetch(`https://${domain_url}/api/v1/accounts/lookup?acct=${username}`).catch(n); .catch(n); // who cares
if (user_req == null || !domain.ok) return null; if (user_req == null) return null;
const user_json = await user_req.json(); const user_json = await user_req.json();
window.localStorage.setItem("fediversemadness_game", JSON.stringify({ username, instance }));
return { return {
USER_ID: user_json.id, FOLLOWERS_LINK: user_json.followers,
INSTANCE: domain_url FOLLOWING_LINK: user_json.following
};
}
async function lookupUser() {
const lsGame = JSON.parse(window.localStorage.getItem("fediversemadness_game"));
if (lsGame != null) {
el_id_user.value = lsGame.username;
el_id_instance.value = lsGame.instance;
} }
} }
lookupUser();
async function doVerif() {
el_id_submitwhoamiFollowers.disabled = true;
el_id_submitwhoamiFollowing.disabled = true;
el_id_submitwhoamiManual.disabled = true;
game = await verify();
el_id_submitwhoamiFollowers.disabled = false;
el_id_submitwhoamiFollowing.disabled = false;
el_id_submitwhoamiManual.disabled = false;
}
el_id_submitwhoamiFollowers.addEventListener("click", async (e) => { el_id_submitwhoamiFollowers.addEventListener("click", async (e) => {
el_id_errorwhoami.innerHTML = ""; el_id_errorwhoami.innerHTML = "";
await doVerif(); el_id_submitwhoamiFollowers.disabled = true;
if (game == null) { el_id_submitwhoamiFollowing.disabled = true;
const result = await verify();
el_id_submitwhoamiFollowers.disabled = false;
el_id_submitwhoamiFollowing.disabled = false;
if (result == null) {
el_id_errorwhoami.innerHTML = "Invalid user!"; el_id_errorwhoami.innerHTML = "Invalid user!";
return false; return false;
} }
game = {
...game,
...result
};
gamemodeFollowers = "followers"; gamemodeFollowers = true;
startFollowers(); getFollowers();
}) })
el_id_submitwhoamiFollowing.addEventListener("click", async (e) => { el_id_submitwhoamiFollowing.addEventListener("click", async (e) => {
el_id_errorwhoami.innerHTML = ""; el_id_errorwhoami.innerHTML = "";
await doVerif(); const result = await verify();
if (game == null) { if (result == null) {
el_id_errorwhoami.innerHTML = "Invalid user!"; el_id_errorwhoami.innerHTML = "Invalid user!";
return false; return false;
} }
game = result;
gamemodeFollowers = "following"; gamemodeFollowers = false;
startFollowing(); getFollowing();
})
el_id_submitwhoamiManual.addEventListener("click", async (e) => {
el_id_errorwhoami.innerHTML = "";
await doVerif();
if (game == null) {
el_id_errorwhoami.innerHTML = "Invalid user!";
return false;
}
gamemodeFollowers = "manual";
startManual();
}) })
// Followers // Followers
@ -125,97 +98,70 @@ const el_id_deselectrandomfollowers = document.querySelector("#deselectrandomfol
const el_id_selectnofollowers = document.querySelector("#selectnofollowers"); const el_id_selectnofollowers = document.querySelector("#selectnofollowers");
const el_id_morefollowers = document.querySelector("#morefollowers"); const el_id_morefollowers = document.querySelector("#morefollowers");
const el_id_submitfollowers = document.querySelector("#submitfollowers"); const el_id_submitfollowers = document.querySelector("#submitfollowers");
const el_id_savehandlesfollowers = document.querySelector("#savehandlesfollowers");
const el_id_loadhandlesfollowers = document.querySelector("#loadhandlesfollowers");
let selectboxes = []; let selectboxes = [];
let userList = []; let userList = [];
let lastId = ""; let lastId = "";
let selectedUsers = []; let selectedUsers = [];
function generateFollowersMap() {
const checks = Array.from(el_id_listfollowers.querySelectorAll(".follower_checkbox"));
let codeMap = {};
checks.forEach((checkbox) => {
const handle = checkbox.parentElement.parentElement.querySelector(".follower_checkname");
codeMap[handle.innerHTML] = checkbox.checked;
});
return codeMap;
}
function readFollowersMap(m) {
if (m == null) return;
const handles = Array.from(el_id_listfollowers.querySelectorAll(".follower_checkname"));
handles.forEach((handle) => {
const checkbox = handle.parentElement.querySelector(".follower_checkbox");
if (m[handle.innerHTML] != null)
checkbox.checked = m[handle.innerHTML];
});
}
function renderNameHTML(name, user) { function renderNameHTML(name, user) {
return `<span>${escapeHtml(name).replace(/:([a-z0-9_-]+?):/gim, (m, p1) => { return escapeHtml(name).replace(/(:[a-z0-9_-]+?:)/gim, (m, p1) => {
const emoji = user.emojis.find(x => x.shortcode == p1); const emoji = user.emojis.find(x => x.name == p1);
if (emoji == null) return null; if (emoji == null) return null;
return `<img src="${emoji.url}" width="16" height="16" title=":${emoji.shortcode}:" />`; return `<img src="${emoji.icon.url}" width="16" height="16" title="${emoji.name}" />`;
})}</span>`; });
}
function startFollowers(reload = true) {
goToState("followers");
if (reload) {
selectboxes = [];
userList = [];
lastId = "";
selectedUsers = [];
el_id_listfollowers.innerHTML = "";
}
getFollowers(!reload);
} }
async function getFollowers(dontLoadNew = false) { async function getFollowers(dontLoadNew = false) {
el_id_whoami.hidden = true;
el_id_followers.hidden = false;
el_id_submitfollowers.disabled = true; el_id_submitfollowers.disabled = true;
el_id_morefollowers.disabled = true; el_id_morefollowers.disabled = true;
let res; let res;
let out; let out;
if (!dontLoadNew) { if (!dontLoadNew) {
el_id_loadingfollowers.innerHTML = `Retrieving followers... Please wait.`; el_id_loadingfollowers.innerHTML = `Retrieving followers... Please wait.`;
res = await fetch(`https://${game.INSTANCE}/api/v1/accounts/${game.USER_ID}/followers${lastId.length > 0 ? `?max_id=${lastId}` : ""}`); res = await fetch(game.FOLLOWERS_LINK);
out = await res.json(); out = await res.json();
} }
el_id_submitfollowers.disabled = false; el_id_submitfollowers.disabled = false;
el_id_morefollowers.disabled = false; el_id_morefollowers.disabled = false;
if (dontLoadNew) return false; if (dontLoadNew) return false;
if (out.length < 1) { if (typeof out.first === "object" && Array.isArray(out.first.orderedItems) && out.first.orderedItems.length < 1) {
el_id_loadingfollowers.innerHTML = `No followers found.`; el_id_loadingfollowers.innerHTML = `No followers found.`;
return false; return false;
} }
el_id_listfollowers.hidden = false; const unsanitizedUsers = await Promise.all(out.first.orderedItems.map(async (x) => {
out.forEach((cuser) => { const fetcher = await fetch(x, {
const element = document.createElement("tr"); headers: {"Accept": "application/activity+json"}
element.innerHTML = `<td><input type="checkbox" class="follower_checkbox" checked onchange="checkSelectedAmtFollowers()" /></td> }).catch(n);
<td><img src="${cuser.avatar}" width="32" height="32" /></td> return
<td class="follower_namelabel"> }));
<a href="${cuser.url}" target="_blank"> console.log(unsanitizedUsers);
${renderNameHTML(cuser.display_name, cuser)} userList.push(...unsanitizedUsers.map(user => ({
</a> fqn: user.preferredUsername + "@" + new URL(user.url).hostname,
</td> avatar: user.icon.url,
<td class="follower_namelabel follower_checkname">@${cuser.fqn}</td>`; display_name: user.name,
el_id_listfollowers.appendChild(element); emojis: user.tag.filter(x => x.type == "Emoji"),
}); fields: user.attachment,
userList.push(...out.map(user => ({ note: user.summary,
fqn: user.fqn || user.acct,
avatar: user.avatar,
bot: user.bot,
created_at: user.created_at,
display_name: user.display_name,
emojis: user.emojis,
fields: user.fields,
id: user.id,
note: user.note,
url: user.url url: user.url
}))); })));
el_id_listfollowers.hidden = false;
el_id_listfollowers.innerHTML += out.reduce((pv, cuser) => {
return pv + `<tr>
<td><input type="checkbox" class="follower_checkbox" checked /></td>
<td><img src="${cuser.avatar}" width="32" height="32" /></td>
<td class="followers_namelabel">
<a href="${cuser.url}" target="_blank">
${renderNameHTML(cuser.display_name, cuser)}
</a>
</td>
<td class="followers_namelabel">@${cuser.fqn || cuser.acct}</td>
</tr>`
}, "");
lastId = userList.at(-1).id; lastId = userList.at(-1).id;
selectboxes = Array.from(el_id_listfollowers.querySelectorAll(".follower_checkbox")); selectboxes = Array.from(el_id_listfollowers.querySelectorAll(".follower_checkbox"));
selectboxes.forEach(x => x.addEventListener("change", checkSelectedAmtFollowers));
checkSelectedAmtFollowers(); checkSelectedAmtFollowers();
} }
@ -245,17 +191,15 @@ el_id_selectnofollowers.addEventListener("click", () => {
checkSelectedAmtFollowers(); checkSelectedAmtFollowers();
}) })
el_id_savehandlesfollowers.addEventListener("click", () => window.localStorage.setItem("fediversemadness_followerssel", JSON.stringify(generateFollowersMap())));
el_id_loadhandlesfollowers.addEventListener("click", () => readFollowersMap(JSON.parse(window.localStorage.getItem("fediversemadness_followerssel"))));
el_id_morefollowers.addEventListener("click", () => getFollowers()); el_id_morefollowers.addEventListener("click", () => getFollowers());
el_id_submitfollowers.addEventListener("click", () => { el_id_submitfollowers.addEventListener("click", () => {
selectedUsers = []; console.log(selectedUsers);
selectboxes.forEach(({checked}, i) => { selectboxes.forEach(({checked}, i) => {
if (checked) selectedUsers.push(userList.at(i)); if (checked) selectedUsers.push(userList.at(i));
}); });
if (selectedUsers.length < 2) { if (selectedUsers.length < 2) {
selectedUsers = [];
el_id_loadingfollowers.innerHTML = `Selected user count is less than 2! (${selectboxes.filter(x => x.checked).length} selected)`; el_id_loadingfollowers.innerHTML = `Selected user count is less than 2! (${selectboxes.filter(x => x.checked).length} selected)`;
return; return;
} }
@ -278,49 +222,17 @@ const el_id_deselectrandomfollowing = document.querySelector("#deselectrandomfol
const el_id_selectnofollowing = document.querySelector("#selectnofollowing"); const el_id_selectnofollowing = document.querySelector("#selectnofollowing");
const el_id_morefollowing = document.querySelector("#morefollowing"); const el_id_morefollowing = document.querySelector("#morefollowing");
const el_id_submitfollowing = document.querySelector("#submitfollowing"); const el_id_submitfollowing = document.querySelector("#submitfollowing");
const el_id_savehandlesfollowing = document.querySelector("#savehandlesfollowing");
const el_id_loadhandlesfollowing = document.querySelector("#loadhandlesfollowing");
function generateFollowingMap() {
const checks = Array.from(el_id_listfollowing.querySelectorAll(".following_checkbox"));
let codeMap = {};
checks.forEach((checkbox) => {
const handle = checkbox.parentElement.parentElement.querySelector(".following_checkname");
codeMap[handle.innerHTML] = checkbox.checked;
});
return codeMap;
}
function readFollowingMap(m) {
if (m == null) return;
const handles = Array.from(el_id_listfollowing.querySelectorAll(".following_checkname"));
handles.forEach((handle) => {
const checkbox = handle.parentElement.querySelector(".following_checkbox");
if (m[handle.innerHTML] != null)
checkbox.checked = m[handle.innerHTML];
});
}
function startFollowing(reload = true) {
goToState("following");
if (reload) {
selectboxes = [];
userList = [];
lastId = "";
selectedUsers = [];
el_id_listfollowing.innerHTML = "";
}
getFollowing(!reload);
}
async function getFollowing(dontLoadNew = false) { async function getFollowing(dontLoadNew = false) {
el_id_whoami.hidden = true;
el_id_following.hidden = false;
el_id_submitfollowing.disabled = true; el_id_submitfollowing.disabled = true;
el_id_morefollowing.disabled = true; el_id_morefollowing.disabled = true;
let res; let res;
let out; let out;
if (!dontLoadNew) { if (!dontLoadNew) {
el_id_loadingfollowing.innerHTML = `Retrieving following... Please wait.`; el_id_loadingfollowing.innerHTML = `Retrieving following... Please wait.`;
res = await fetch(`https://${game.INSTANCE}/api/v1/accounts/${game.USER_ID}/following${lastId.length > 0 ? `?max_id=${lastId}` : ""}`); res = await fetch(`https://${game.INSTANCE}/api/v1/accounts/${game.USER_ID}/following?limit=68${lastId.length > 0 ? `&max_id=${lastId}` : ""}`);
out = await res.json(); out = await res.json();
} }
el_id_submitfollowing.disabled = false; el_id_submitfollowing.disabled = false;
@ -331,18 +243,18 @@ async function getFollowing(dontLoadNew = false) {
return false; return false;
} }
el_id_listfollowing.hidden = false; el_id_listfollowing.hidden = false;
out.forEach((cuser) => { el_id_listfollowing.innerHTML += out.reduce((pv, cuser) => {
const element = document.createElement("tr"); return pv + `<tr>
element.innerHTML = `<td><input type="checkbox" class="following_checkbox" checked onchange="checkSelectedAmtFollowing()" /></td> <td><input type="checkbox" class="follower_checkbox" checked /></td>
<td><img src="${cuser.avatar}" width="32" height="32" /></td> <td><img src="${cuser.avatar}" width="32" height="32" /></td>
<td class="following_namelabel"> <td class="following_namelabel">
<a href="${cuser.url}" target="_blank"> <a href="${cuser.url}" target="_blank">
${renderNameHTML(cuser.display_name, cuser)} ${renderNameHTML(cuser.display_name, cuser)}
</a> </a>
</td> </td>
<td class="following_namelabel following_checkname">@${cuser.fqn}</td>`; <td class="following_namelabel">@${cuser.fqn}</td>
el_id_listfollowing.appendChild(element); </tr>`
}); }, "");
userList.push(...out.map(user => ({ userList.push(...out.map(user => ({
fqn: user.fqn, fqn: user.fqn,
avatar: user.avatar, avatar: user.avatar,
@ -353,10 +265,12 @@ async function getFollowing(dontLoadNew = false) {
fields: user.fields, fields: user.fields,
id: user.id, id: user.id,
note: user.note, note: user.note,
url: user.url username: user.url,
username: user.username
}))); })));
lastId = userList.at(-1).id; lastId = userList.at(-1).id;
selectboxes = Array.from(el_id_listfollowing.querySelectorAll(".following_checkbox")); selectboxes = Array.from(el_id_listfollowing.querySelectorAll(".follower_checkbox"));
selectboxes.forEach(x => x.addEventListener("change", checkSelectedAmtFollowing));
checkSelectedAmtFollowing(); checkSelectedAmtFollowing();
} }
@ -386,17 +300,15 @@ el_id_selectnofollowing.addEventListener("click", () => {
checkSelectedAmtFollowing(); checkSelectedAmtFollowing();
}) })
el_id_savehandlesfollowing.addEventListener("click", () => window.localStorage.setItem("fediversemadness_followingsel", JSON.stringify(generateFollowingMap())));
el_id_loadhandlesfollowing.addEventListener("click", () => readFollowingMap(JSON.parse(window.localStorage.getItem("fediversemadness_followingsel"))));
el_id_morefollowing.addEventListener("click", () => getFollowing()); el_id_morefollowing.addEventListener("click", () => getFollowing());
el_id_submitfollowing.addEventListener("click", () => { el_id_submitfollowing.addEventListener("click", () => {
selectedUsers = []; console.log(selectedUsers);
selectboxes.forEach(({checked}, i) => { selectboxes.forEach(({checked}, i) => {
if (checked) selectedUsers.push(userList.at(i)); if (checked) selectedUsers.push(userList.at(i));
}); });
if (selectedUsers.length < 2) { if (selectedUsers.length < 2) {
selectedUsers = [];
el_id_loadingfollowing.innerHTML = `Selected user count is less than 2! (${selectboxes.filter(x => x.checked).length} selected)`; el_id_loadingfollowing.innerHTML = `Selected user count is less than 2! (${selectboxes.filter(x => x.checked).length} selected)`;
return; return;
} }
@ -407,122 +319,6 @@ function checkSelectedAmtFollowing() {
el_id_loadingfollowing.innerHTML = `${selectboxes.filter(x => x.checked).length} selected`; el_id_loadingfollowing.innerHTML = `${selectboxes.filter(x => x.checked).length} selected`;
} }
// Manual
const el_id_manual = document.querySelector("#manual");
const el_id_loadingmanual = document.querySelector("#loadingmanual");
const el_id_listmanual = document.querySelector("#listmanual");
const el_id_listmanualvalues = document.querySelector("#listmanualvalues");
const el_id_selectallmanual = document.querySelector("#selectallmanual");
const el_id_selectrandommanual = document.querySelector("#selectrandommanual");
const el_id_deselectrandommanual = document.querySelector("#deselectrandommanual");
const el_id_selectnomanual = document.querySelector("#selectnomanual");
const el_id_manualuser = document.querySelector("#manualuser");
const el_id_manualinstance = document.querySelector("#manualinstance");
const el_id_moremanual = document.querySelector("#moremanual");
const el_id_submitmanual = document.querySelector("#submitmanual");
function startManual(reload = true) {
goToState("manual");
if (reload) {
selectboxes = [];
userList = [];
lastId = "";
selectedUsers = [];
el_id_listmanual.innerHTML = "";
}
checkSelectedAmtManual();
}
async function addUserToManual() {
const username = el_id_manualuser.value.replace(/[^a-z0-9_]/gim, "");
const instance = el_id_manualinstance.value.replace(/@/gim, "");
el_id_manualuser.value = username;
el_id_manualinstance.value = instance;
if (username.length < 1) return null;
if (instance.length < 1) return null;
el_id_submitmanual.disabled = true;
el_id_moremanual.disabled = true;
el_id_loadingmanual.innerHTML = `Finding user...`;
let res = await fetch(`https://${game.INSTANCE}/api/v1/accounts/lookup?acct=${username}@${instance}`);
let out = await res.json();
el_id_submitmanual.disabled = false;
el_id_moremanual.disabled = false;
if (out.length < 1) {
el_id_loadingmanual.innerHTML = `No manual found.`;
return false;
}
el_id_listmanual.hidden = false;
const element = document.createElement("tr");
element.innerHTML = `<td><input type="checkbox" class="manual_checkbox" checked onchange="checkSelectedAmtManual()" /></td>
<td><img src="${out.avatar}" width="32" height="32" /></td>
<td class="manual_namelabel">
<a href="${out.url}" target="_blank">
${renderNameHTML(out.display_name, out)}
</a>
</td>
<td class="manual_namelabel manual_checkname">@${out.fqn}</td>`;
el_id_listmanual.appendChild(element);
userList.push({
fqn: out.fqn,
avatar: out.avatar,
bot: out.bot,
created_at: out.created_at,
display_name: out.display_name,
emojis: out.emojis,
fields: out.fields,
id: out.id,
note: out.note,
url: out.url
});
selectboxes = Array.from(el_id_listmanual.querySelectorAll(".manual_checkbox"));
checkSelectedAmtManual();
}
el_id_selectallmanual.addEventListener("click", () => {
selectboxes.forEach(x => x.checked = true);
checkSelectedAmtManual();
})
el_id_selectrandommanual.addEventListener("click", () => {
const unselectedPick = selectboxes.filter(x => !x.checked);
const selected = unselectedPick[Math.floor(Math.random() * (unselectedPick.length - 1))];
if (selected == null) return;
selected.checked = true;
checkSelectedAmtManual();
})
el_id_deselectrandommanual.addEventListener("click", () => {
const selectedPick = selectboxes.filter(x => x.checked);
const selected = selectedPick[Math.floor(Math.random() * (selectedPick.length - 1))];
if (selected == null) return;
selected.checked = false;
checkSelectedAmtManual();
})
el_id_selectnomanual.addEventListener("click", () => {
selectboxes.forEach(x => x.checked = false);
checkSelectedAmtManual();
})
el_id_moremanual.addEventListener("click", () => addUserToManual());
el_id_submitmanual.addEventListener("click", () => {
selectedUsers = [];
selectboxes.forEach(({checked}, i) => {
if (checked) selectedUsers.push(userList.at(i));
});
if (selectedUsers.length < 2) {
el_id_loadingmanual.innerHTML = `Selected user count is less than 2! (${selectboxes.filter(x => x.checked).length} selected)`;
return;
}
sortSelectedUsers();
})
function checkSelectedAmtManual() {
el_id_loadingmanual.innerHTML = `${selectboxes.filter(x => x.checked).length} selected`;
}
// Brackets // Brackets
function shuf(array) { function shuf(array) {
@ -549,14 +345,14 @@ const el_id_gameUserOne = document.querySelector("#gameUserOne");
const el_id_gameUserOneImage = document.querySelector("#gameUserOneImage"); const el_id_gameUserOneImage = document.querySelector("#gameUserOneImage");
const el_id_gameUserOneName = document.querySelector("#gameUserOneName"); const el_id_gameUserOneName = document.querySelector("#gameUserOneName");
const el_id_gameUserOneId = document.querySelector("#gameUserOneId"); const el_id_gameUserOneId = document.querySelector("#gameUserOneId");
// const el_id_gameUserOneNote = document.querySelector("#gameUserOneNote"); const el_id_gameUserOneNote = document.querySelector("#gameUserOneNote");
// const el_id_gameUserOneFields = document.querySelector("#gameUserOneFields"); const el_id_gameUserOneFields = document.querySelector("#gameUserOneFields");
const el_id_gameUserTwo = document.querySelector("#gameUserTwo"); const el_id_gameUserTwo = document.querySelector("#gameUserTwo");
const el_id_gameUserTwoImage = document.querySelector("#gameUserTwoImage"); const el_id_gameUserTwoImage = document.querySelector("#gameUserTwoImage");
const el_id_gameUserTwoName = document.querySelector("#gameUserTwoName"); const el_id_gameUserTwoName = document.querySelector("#gameUserTwoName");
const el_id_gameUserTwoId = document.querySelector("#gameUserTwoId"); const el_id_gameUserTwoId = document.querySelector("#gameUserTwoId");
// const el_id_gameUserTwoNote = document.querySelector("#gameUserTwoNote"); const el_id_gameUserTwoNote = document.querySelector("#gameUserTwoNote");
// const el_id_gameUserTwoFields = document.querySelector("#gameUserTwoFields"); const el_id_gameUserTwoFields = document.querySelector("#gameUserTwoFields");
const el_id_gameSubmitLeft = document.querySelector("#gameSubmitLeft"); const el_id_gameSubmitLeft = document.querySelector("#gameSubmitLeft");
const el_id_gameSubmitRight = document.querySelector("#gameSubmitRight"); const el_id_gameSubmitRight = document.querySelector("#gameSubmitRight");
const el_id_gameBracketLevel = document.querySelector("#gameBracketLevel"); const el_id_gameBracketLevel = document.querySelector("#gameBracketLevel");
@ -567,16 +363,19 @@ let curDepth = 0;
let curFight = 0; let curFight = 0;
function sortSelectedUsers() { function sortSelectedUsers() {
goToState("game"); if (gamemodeFollowers)
state = []; el_id_followers.hidden = true;
curDepth = 0; else
curFight = 0; el_id_following.hidden = true;
el_id_game.hidden = false;
state.push(chunk(shuf(selectedUsers), 2)); state.push(chunk(shuf(selectedUsers), 2));
state.push([]); state.push([]);
prepareGameStage(); prepareGameStage();
} }
function prepareGameStage() { function prepareGameStage() {
el_id_gameSubmitLeft.disabled = true;
el_id_gameSubmitRight.disabled = true;
el_id_gameSubmitLeft.innerHTML = "Please wait..."; el_id_gameSubmitLeft.innerHTML = "Please wait...";
el_id_gameSubmitRight.innerHTML = "Please wait..."; el_id_gameSubmitRight.innerHTML = "Please wait...";
el_id_gameUserOneImage.src = ""; el_id_gameUserOneImage.src = "";
@ -585,15 +384,15 @@ function prepareGameStage() {
el_id_gameBracketFight.innerHTML = "Round " + (curFight + 1); el_id_gameBracketFight.innerHTML = "Round " + (curFight + 1);
const curSubStage = state[curDepth][curFight]; const curSubStage = state[curDepth][curFight];
el_id_gameUserOneImage.src = curSubStage[0].avatar; el_id_gameUserOneImage.src = curSubStage[0].avatar;
el_id_gameUserOneName.innerHTML = `<a href="${curSubStage[0].url}" target="_blank">${renderNameHTML(escapeHtml(curSubStage[0].display_name), curSubStage[0])}</a>`; el_id_gameUserOneName.innerHTML = renderNameHTML(escapeHtml(curSubStage[0].display_name), curSubStage[0]);
el_id_gameUserOneId.innerHTML = "@" + curSubStage[0].fqn; el_id_gameUserOneId.innerHTML = "@" + curSubStage[0].fqn;
// el_id_gameUserOneNote.innerHTML = escapeHtml(curSubStage[0].note); el_id_gameUserOneNote.innerHTML = escapeHtml(curSubStage[0].note);
// el_id_gameUserOneFields.innerHTML = curSubStage[0].fields.reduce((pv, fields) => pv + `<tr><td>${escapeHtml(fields.name)}</td><td>${escapeHtml(fields.value)}</td></tr>`, ""); el_id_gameUserOneFields.innerHTML = curSubStage[0].fields.reduce((pv, fields) => pv + `<tr><td>${escapeHtml(fields.name)}</td><td>${escapeHtml(fields.value)}</td></tr>`, "");
el_id_gameUserTwoImage.src = curSubStage[1].avatar; el_id_gameUserTwoImage.src = curSubStage[1].avatar;
el_id_gameUserTwoName.innerHTML = `<a href="${curSubStage[1].url}" target="_blank">${renderNameHTML(escapeHtml(curSubStage[1].display_name), curSubStage[1])}</a>`; el_id_gameUserTwoName.innerHTML = renderNameHTML(escapeHtml(curSubStage[1].display_name), curSubStage[1]);
el_id_gameUserTwoId.innerHTML = "@" + curSubStage[1].fqn; el_id_gameUserTwoId.innerHTML = "@" + curSubStage[1].fqn;
// el_id_gameUserTwoNote.innerHTML = escapeHtml(curSubStage[1].note); el_id_gameUserTwoNote.innerHTML = escapeHtml(curSubStage[1].note);
// el_id_gameUserTwoFields.innerHTML = curSubStage[1].fields.reduce((pv, fields) => pv + `<tr><td>${escapeHtml(fields.name)}</td><td>${escapeHtml(fields.value)}</td></tr>`, ""); el_id_gameUserTwoFields.innerHTML = curSubStage[1].fields.reduce((pv, fields) => pv + `<tr><td>${escapeHtml(fields.name)}</td><td>${escapeHtml(fields.value)}</td></tr>`, "");
el_id_gameSubmitLeft.disabled = false; el_id_gameSubmitLeft.disabled = false;
el_id_gameSubmitRight.disabled = false; el_id_gameSubmitRight.disabled = false;
el_id_gameSubmitLeft.innerHTML = "Pick " + "@" + curSubStage[0].fqn; el_id_gameSubmitLeft.innerHTML = "Pick " + "@" + curSubStage[0].fqn;
@ -642,94 +441,44 @@ const el_id_winnerUser = document.querySelector("#winnerUser");
const el_id_winnerUserImage = document.querySelector("#winnerUserImage"); const el_id_winnerUserImage = document.querySelector("#winnerUserImage");
const el_id_winnerUserName = document.querySelector("#winnerUserName"); const el_id_winnerUserName = document.querySelector("#winnerUserName");
const el_id_winnerUserId = document.querySelector("#winnerUserId"); const el_id_winnerUserId = document.querySelector("#winnerUserId");
// const el_id_winnerUserNote = document.querySelector("#winnerUserNote"); const el_id_winnerUserNote = document.querySelector("#winnerUserNote");
// const el_id_winnerUserFields = document.querySelector("#winnerUserFields"); const el_id_winnerUserFields = document.querySelector("#winnerUserFields");
const el_id_winnermessage = document.querySelector("#winnermessage"); const el_id_winnermessage = document.querySelector("#winnermessage");
const el_id_winnerusers = document.querySelector("#winnerusers"); const el_id_winnerusers = document.querySelector("#winnerusers");
const el_id_winnerPlayAgain = document.querySelector("#winnerPlayAgain"); const el_id_winnerPlayAgain = document.querySelector("#winnerPlayAgain");
function endGame() { function endGame() {
goToState("winner"); el_id_game.hidden = true;
el_id_winner.hidden = false;
const winningPlayer = state[curDepth][curFight][0]; const winningPlayer = state[curDepth][curFight][0];
el_id_winnerUserImage.src = winningPlayer.avatar; el_id_winnerUserImage.src = winningPlayer.avatar;
el_id_winnerUserName.innerHTML = `<a href="${winningPlayer.url}" target="_blank">${renderNameHTML(escapeHtml(winningPlayer.display_name), winningPlayer)}</a>`; el_id_winnerUserName.innerHTML = renderNameHTML(escapeHtml(winningPlayer.display_name), winningPlayer);
el_id_winnerUserId.innerHTML = "@" + winningPlayer.fqn; el_id_winnerUserId.innerHTML = "@" + winningPlayer.fqn;
// el_id_winnerUserNote.innerHTML = escapeHtml(winningPlayer.note); el_id_winnerUserNote.innerHTML = escapeHtml(winningPlayer.note);
// el_id_winnerUserFields.innerHTML = winningPlayer.fields.reduce((pv, fields) => pv + `<tr><td>${escapeHtml(fields.name)}</td><td>${escapeHtml(fields.value)}</td></tr>`, ""); el_id_winnerUserFields.innerHTML = winningPlayer.fields.reduce((pv, fields) => pv + `<tr><td>${escapeHtml(fields.name)}</td><td>${escapeHtml(fields.value)}</td></tr>`, "");
el_id_winnermessage.innerHTML = `Congratulations, ${renderNameHTML(escapeHtml(winningPlayer.display_name), winningPlayer)}! You won the bracket!`; el_id_winnermessage.innerHTML = `Congratulations, ${renderNameHTML(escapeHtml(winningPlayer.display_name), winningPlayer)}! You won the bracket!`;
el_id_winnerusers.innerHTML = state.reduce((pv, cs) => { el_id_winnerusers.innerHTML = `<div>${state.reduce((pv, cs) => {
return pv + `<div class="flexUser">${cs.reduce((pvus, cus) => { return pv + `<div class="flexUser">${cs.reduce((pvus, cus) => {
console.log(cus); return pvus + cus.reduce((pvu, cu) => {
return pvus + `<div class="flexUserGroup"><div class="flexUserUser"> return pvu + `<div class="flexUserUser">
<img src="${cus[0].avatar}" width="64" height="64" /> <img src="${cu.avatar}" width="64" height="64" />
<h3><a href="${cus[0].url}" target="_blank">${renderNameHTML(escapeHtml(cus[0].display_name), cus[0])}</a></h3> <h3>${renderNameHTML(escapeHtml(cu.display_name), cu)}</h3>
<p>@${cus[0].fqn}</p> <p>@${cu.fqn}</p>
</div>${cus[1] ? `<div class="flexUserUser"> </div>`
<img src="${cus[1].avatar}" width="64" height="64" /> }, "")
<h3><a href="${cus[1].url}" target="_blank">${renderNameHTML(escapeHtml(cus[1].display_name), cus[1])}</a></h3>
<p>@${cus[1].fqn}</p>
</div>` : ""}</div>`
}, "")}</div>` }, "")}</div>`
}, ""); }, "")}</div>`
} }
el_id_winnerPlayAgain.addEventListener("click", (x) => { el_id_winnerPlayAgain.addEventListener("click", (x) => {
el_id_winner.hidden = true;
state = []; state = [];
selectedUsers = []; selectedUsers = [];
curDepth = 0; curDepth = 0;
curFight = 0; curFight = 0;
switch (gamemodeFollowers) { if (gamemodeFollowers)
case "followers": getFollowers(true);
startFollowers(false); else
break; getFollowing(true);
case "following":
startFollowing(false);
break;
case "manual":
startManual(false);
break;
}
}) })
// Game state
const gameStates = new Map([
["whoami", {
state: el_id_whoami
}],
["followers", {
state: el_id_followers
}],
["following", {
state: el_id_following
}],
["manual", {
state: el_id_manual
}],
["game", {
state: el_id_game,
links: {
"winner": () => gamemodeFollowers
}
}],
["winner", {
state: el_id_winner
}]
]);
let prevState = "";
function goToState(state) {
window.location.hash = state;
const curState = gameStates.get(state);
if (curState.links != null)
if (curState.links[prevState])
return goToState(curState.links[prevState]());
gameStates.forEach(x => x.state.hidden = true);
curState.state.hidden = false;
prevState = state;
}
goToState("whoami");
addEventListener("popstate", () => goToState(window.location.hash.slice(1)));

View file

@ -53,17 +53,17 @@
.flexUsersVert { .flexUsersVert {
display: flex; display: flex;
flex-direction: row; flex-direction: column;
align-items: stretch; align-items: safe center;
overflow: auto; overflow: auto;
gap: 0.5em; gap: 0.5em;
} }
.flexUser { .flexUser {
display: flex; display: flex;
flex-direction: column;
flex-wrap: nowrap; flex-wrap: nowrap;
justify-content: space-around; justify-content: space-evenly;
width: 100%;
gap: 0.5em; gap: 0.5em;
} }
@ -71,21 +71,10 @@
text-align: center; text-align: center;
width: 256px; width: 256px;
min-width: 256px; min-width: 256px;
height: 150px;
min-height: 150px;
box-sizing: content-box;
overflow: hidden; overflow: hidden;
word-wrap: break-word; word-wrap: break-word;
} }
.flexUserGroup {
display: flex;
flex-direction: column;
flex-wrap: nowrap;
justify-content: space-around;
gap: 0.5em;
}
.flexUserUser * { .flexUserUser * {
word-wrap: break-word; word-wrap: break-word;
} }

View file

@ -1,96 +0,0 @@
# https://seirdy.one/robots.txt
User-agent: *
Disallow: /noindex/
Disallow: /misc/
# I opt out of online advertising so malware that injects ads on my site won't get paid.
# You should do the same. my ads.txt file contains a standard placeholder to forbid any
# compliant ad networks from paying for ad placement on my domain.
User-Agent: Adsbot
Disallow: /
Allow: /ads.txt
Allow: /app-ads.txt
## IP-violation scanners ##
# The next three are borrowed from https://www.videolan.org/robots.txt
# > This robot collects content from the Internet for the sole purpose of # helping educational institutions prevent plagiarism. [...] we compare student papers against the content we find on the Internet to see if we # can find similarities. (http://www.turnitin.com/robot/crawlerinfo.html)
# --> fuck off.
User-Agent: TurnitinBot
Disallow: /
# > NameProtect engages in crawling activity in search of a wide range of brand and other intellectual property violations that may be of interest to our clients. (http://www.nameprotect.com/botinfo.html)
# --> fuck off.
User-Agent: NPBot
Disallow: /
# iThenticate is a new service we have developed to combat the piracy of intellectual property and ensure the originality of written work for# publishers, non-profit agencies, corporations, and newspapers. (http://www.slysearch.com/)
# --> fuck off.
User-Agent: SlySearch
Disallow: /
# BLEXBot assists internet marketers to get information on the link structure of sites and their interlinking on the web, to avoid any technical and possible legal issues and improve overall online experience. (http://webmeup-crawler.com/)
# --> fuck off.
User-Agent: BLEXBot
Disallow: /
# Providing Intellectual Property professionals with superior brand protection services by artfully merging the latest technology with expert analysis. (https://www.checkmarknetwork.com/spider.html/)
# "The Internet is just way to big to effectively police alone." (ACTUAL quote)
# --> fuck off.
User-agent: CheckMarkNetwork/1.0 (+https://www.checkmarknetwork.com/spider.html)
Disallow: /
# Stop trademark violations and affiliate non-compliance in paid search. Automatically monitor your partner and affiliates online marketing to protect yourself from harmful brand violations and regulatory risks. We regularly crawl websites on behalf of our clients to ensure content compliance with brand and regulatory guidelines. (https://www.brandverity.com/why-is-brandverity-visiting-me)
# --> fuck off.
User-agent: BrandVerity/1.0
Disallow: /
## Misc. icky stuff ##
# Pipl assembles online identity information from multiple independent sources to create the most complete picture of a digital identity and connect it to real people and their offline identity records. When all the fragments of online identity data are collected, connected, and corroborated, the result is a more trustworthy identity.
# --> fuck off.
User-agent: PiplBot
Disallow: /
## Gen-AI data scrapers ##
# Eat shit, OpenAI.
User-agent: ChatGPT-User
Disallow: /
User-agent: GPTBot
Disallow: /
# Official way to opt-out of Google's generative AI training:
# <https://developers.google.com/search/docs/crawling-indexing/overview-google-crawlers>
User-agent: Google-Extended
Disallow: /
# There isn't any public documentation for this AFAICT.
# Reuters thinks this works so I might as well give it a shot.
User-agent: anthropic-ai
Disallow: /
User-agent: Claude-Web
Disallow: /
# FacebookBot crawls public web pages to improve language models for our speech recognition technology.
# <https://developers.facebook.com/docs/sharing/bot/?_fb_noscript=1>
User-Agent: FacebookBot
Disallow: /
# I'm not blocking CCBot for now. It publishes a free index for anyone to use.
# Googe used this to train the initial version of Bard (now called Gemini).
# I allow CCBot since its index is also used for upstart/hobbyist search engines
# like Alexandria and for genuinely useful academic work I personally like.
# I allow Owler for similar reasons:
# <https://openwebsearch.eu/owler/#owler-opt-out>
# <https://openwebsearch.eu/common-goals-with-common-crawl/>.
# Omgilibot/Omgili is similar to CCBot, except it sells the scrape results.
# I'm not familiar enough with Omgili to make a call here.
# In the long run, my embedded robots meta-tags and headers could cover gen-AI
# I don't block cohere-ai or Perplexitybot: they don't appear to actually scrape data for LLM training purposes. The crawling powers search engines with integrated pre-trained LLMs.
# TODO: investigate whether YouBot scrapes to train its own in-house LLM.
Sitemap: https://seirdy.one/sitemap.xml