Compare commits

...

15 commits

4 changed files with 528 additions and 131 deletions

View file

@ -18,11 +18,12 @@
<section>
<h1>Fediverse Madness</h1>
<p>Competitive bracket-based comparisons of Fediverse users.</p>
<p>Requires JavaScript.</p>
</section>
<section id="accessibility" hidden></section>
</header>
<main>
<section id="whoami">
<section id="whoami" hidden>
<h3>Who are you?</h3>
<span class="userinputbox">
<span>@</span>
@ -34,6 +35,7 @@
<hr />
<button id="submitwhoami_followers">Play (Followers)</button>
<button id="submitwhoami_following">Play (Following)</button>
<button id="submitwhoami_manual">Play (Manual)</button>
<p id="errorwhoami"></p>
</section>
<section id="followers" hidden>
@ -53,7 +55,11 @@
<button id="selectrandomfollowers">Select random</button>
<button id="deselectrandomfollowers">Deselect random</button>
</div>
<button id="morefollowers">Load more</button>
<div>
<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>
</section>
<section id="following" hidden>
@ -73,9 +79,41 @@
<button id="selectrandomfollowing">Select random</button>
<button id="deselectrandomfollowing">Deselect random</button>
</div>
<button id="morefollowing">Load more</button>
<div>
<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>
</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>
<h3>Fight!</h3>
<p id="gameBracketLevel">Bracket Level ...</p>
@ -86,9 +124,9 @@
<div>
<h3 id="gameUserOneName"></h3>
<p id="gameUserOneId"></p>
<p id="gameUserOneNote"></p>
<!-- <p id="gameUserOneNote"></p>
<table id="gameUserOneFields">
</table>
</table> -->
</div>
</div>
<div class="gridUser" id="gameUserTwo">
@ -96,9 +134,9 @@
<div>
<h3 id="gameUserTwoName"></h3>
<p id="gameUserTwoId"></p>
<p id="gameUserTwoNote"></p>
<!-- <p id="gameUserTwoNote"></p>
<table id="gameUserTwoFields">
</table>
</table> -->
</div>
</div>
</div>
@ -114,9 +152,9 @@
<div>
<h3 id="winnerUserName"></h3>
<p id="winnerUserId"></p>
<p id="winnerUserNote"></p>
<!-- <p id="winnerUserNote"></p>
<table id="winnerUserFields">
</table>
</table> -->
</div>
</div>
<h3>Bracket</h3>
@ -129,10 +167,7 @@
<section id="accessibility" hidden></section>
<script src="/scripts/accessibility.js"></script>
<script>
let game = {
USER_ID: "Abg3KCIlHi1Q2Gzx0y",
INSTANCE: "local.abtmtr.link"
};
let game = null;
</script>
<script src="./script.js"></script>
<script src="./scripts/game.js"></script>

View file

@ -10,13 +10,16 @@ function escapeHtml(unsafe)
.replace(/<\/?object\/?>/g, "");
}
const n=x=>null;
const el_id_user = document.querySelector("#user");
const el_id_instance = document.querySelector("#instance");
const el_id_whoami = document.querySelector("#whoami");
const el_id_submitwhoamiFollowers = document.querySelector("#submitwhoami_followers");
const el_id_submitwhoamiFollowing = document.querySelector("#submitwhoami_following");
const el_id_submitwhoamiManual = document.querySelector("#submitwhoami_manual");
const el_id_errorwhoami = document.querySelector("#errorwhoami");
let gamemodeFollowers;
let gamemodeFollowers = "";
el_id_user.addEventListener("input", (e) => {
console.log(e);
@ -33,56 +36,81 @@ async function verify() {
const instance = el_id_instance.value.replace(/@/gim, "");
el_id_user.value = username;
el_id_instance.value = instance;
el_id_submitwhoamiFollowers.disabled = true;
el_id_submitwhoamiFollowing.disabled = true;
if (username.length < 1) return null;
if (instance.length < 1) return null;
let user_req;
try {
user_req = await fetch(`https://${instance}/api/v1/accounts/lookup?acct=${username}`);
} catch (err) {
return null;
}
// webfinger domain delegation
const domain = await fetch(`https://${instance}/.well-known/webfinger?resource=acct:${username}@${instance}`).catch(n);
if (domain == null || !domain.ok) return null;
const domain_json = (await domain.json());
const domain_url = new URL(domain_json.links.find(x=>x.type=="application/activity+json").href).hostname;
const user_req = await fetch(`https://${domain_url}/api/v1/accounts/lookup?acct=${username}`).catch(n);
if (user_req == null || !domain.ok) return null;
const user_json = await user_req.json();
window.localStorage.setItem("fediversemadness_game", JSON.stringify({ username, instance }));
return {
USER_ID: user_json.id,
INSTANCE: domain_url
};
}
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;
if (user_req.ok) return {
USER_ID: user_json.id,
INSTANCE: instance
};
else return null;
el_id_submitwhoamiManual.disabled = false;
}
el_id_submitwhoamiFollowers.addEventListener("click", async (e) => {
el_id_errorwhoami.innerHTML = "";
const result = await verify();
if (result == null) {
await doVerif();
if (game == null) {
el_id_errorwhoami.innerHTML = "Invalid user!";
return false;
}
game = {
...game,
...result
};
gamemodeFollowers = true;
getFollowers();
gamemodeFollowers = "followers";
startFollowers();
})
el_id_submitwhoamiFollowing.addEventListener("click", async (e) => {
el_id_errorwhoami.innerHTML = "";
const result = await verify();
if (result == null) {
await doVerif();
if (game == null) {
el_id_errorwhoami.innerHTML = "Invalid user!";
return false;
}
game = {
...game,
...result
};
gamemodeFollowers = false;
getFollowing();
gamemodeFollowers = "following";
startFollowing();
})
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
@ -97,22 +125,54 @@ const el_id_deselectrandomfollowers = document.querySelector("#deselectrandomfol
const el_id_selectnofollowers = document.querySelector("#selectnofollowers");
const el_id_morefollowers = document.querySelector("#morefollowers");
const el_id_submitfollowers = document.querySelector("#submitfollowers");
const el_id_savehandlesfollowers = document.querySelector("#savehandlesfollowers");
const el_id_loadhandlesfollowers = document.querySelector("#loadhandlesfollowers");
let selectboxes = [];
let userList = [];
let lastId = "";
let selectedUsers = [];
function renderNameHTML(name, user) {
return escapeHtml(name).replace(/:([a-z0-9_-]+?):/gim, (m, p1) => {
const emoji = user.emojis.find(x => x.shortcode == p1);
if (emoji == null) return null;
return `<img src="${emoji.url}" width="16" height="16" title=":${emoji.shortcode}:" />`;
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) {
return `<span>${escapeHtml(name).replace(/:([a-z0-9_-]+?):/gim, (m, p1) => {
const emoji = user.emojis.find(x => x.shortcode == p1);
if (emoji == null) return null;
return `<img src="${emoji.url}" width="16" height="16" title=":${emoji.shortcode}:" />`;
})}</span>`;
}
function startFollowers(reload = true) {
goToState("followers");
if (reload) {
selectboxes = [];
userList = [];
lastId = "";
selectedUsers = [];
el_id_listfollowers.innerHTML = "";
}
getFollowers(!reload);
}
async function getFollowers(dontLoadNew = false) {
el_id_whoami.hidden = true;
el_id_followers.hidden = false;
el_id_submitfollowers.disabled = true;
el_id_morefollowers.disabled = true;
let res;
@ -130,18 +190,18 @@ async function getFollowers(dontLoadNew = false) {
return false;
}
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>`
}, "");
out.forEach((cuser) => {
const element = document.createElement("tr");
element.innerHTML = `<td><input type="checkbox" class="follower_checkbox" checked onchange="checkSelectedAmtFollowers()" /></td>
<td><img src="${cuser.avatar}" width="32" height="32" /></td>
<td class="follower_namelabel">
<a href="${cuser.url}" target="_blank">
${renderNameHTML(cuser.display_name, cuser)}
</a>
</td>
<td class="follower_namelabel follower_checkname">@${cuser.fqn}</td>`;
el_id_listfollowers.appendChild(element);
});
userList.push(...out.map(user => ({
fqn: user.fqn || user.acct,
avatar: user.avatar,
@ -152,12 +212,10 @@ async function getFollowers(dontLoadNew = false) {
fields: user.fields,
id: user.id,
note: user.note,
username: user.url,
username: user.username
url: user.url
})));
lastId = userList.at(-1).id;
selectboxes = Array.from(el_id_listfollowers.querySelectorAll(".follower_checkbox"));
selectboxes.forEach(x => x.addEventListener("change", checkSelectedAmtFollowers));
checkSelectedAmtFollowers();
}
@ -187,15 +245,17 @@ el_id_selectnofollowers.addEventListener("click", () => {
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_submitfollowers.addEventListener("click", () => {
console.log(selectedUsers);
selectedUsers = [];
selectboxes.forEach(({checked}, i) => {
if (checked) selectedUsers.push(userList.at(i));
});
if (selectedUsers.length < 2) {
selectedUsers = [];
el_id_loadingfollowers.innerHTML = `Selected user count is less than 2! (${selectboxes.filter(x => x.checked).length} selected)`;
return;
}
@ -218,17 +278,49 @@ const el_id_deselectrandomfollowing = document.querySelector("#deselectrandomfol
const el_id_selectnofollowing = document.querySelector("#selectnofollowing");
const el_id_morefollowing = document.querySelector("#morefollowing");
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) {
el_id_whoami.hidden = true;
el_id_following.hidden = false;
el_id_submitfollowing.disabled = true;
el_id_morefollowing.disabled = true;
let res;
let out;
if (!dontLoadNew) {
el_id_loadingfollowing.innerHTML = `Retrieving following... Please wait.`;
res = await fetch(`https://${game.INSTANCE}/api/v1/accounts/${game.USER_ID}/following?limit=68${lastId.length > 0 ? `&max_id=${lastId}` : ""}`);
res = await fetch(`https://${game.INSTANCE}/api/v1/accounts/${game.USER_ID}/following${lastId.length > 0 ? `?max_id=${lastId}` : ""}`);
out = await res.json();
}
el_id_submitfollowing.disabled = false;
@ -239,18 +331,18 @@ async function getFollowing(dontLoadNew = false) {
return false;
}
el_id_listfollowing.hidden = false;
el_id_listfollowing.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="following_namelabel">
<a href="${cuser.url}" target="_blank">
${renderNameHTML(cuser.display_name, cuser)}
</a>
</td>
<td class="following_namelabel">@${cuser.fqn}</td>
</tr>`
}, "");
out.forEach((cuser) => {
const element = document.createElement("tr");
element.innerHTML = `<td><input type="checkbox" class="following_checkbox" checked onchange="checkSelectedAmtFollowing()" /></td>
<td><img src="${cuser.avatar}" width="32" height="32" /></td>
<td class="following_namelabel">
<a href="${cuser.url}" target="_blank">
${renderNameHTML(cuser.display_name, cuser)}
</a>
</td>
<td class="following_namelabel following_checkname">@${cuser.fqn}</td>`;
el_id_listfollowing.appendChild(element);
});
userList.push(...out.map(user => ({
fqn: user.fqn,
avatar: user.avatar,
@ -261,12 +353,10 @@ async function getFollowing(dontLoadNew = false) {
fields: user.fields,
id: user.id,
note: user.note,
username: user.url,
username: user.username
url: user.url
})));
lastId = userList.at(-1).id;
selectboxes = Array.from(el_id_listfollowing.querySelectorAll(".follower_checkbox"));
selectboxes.forEach(x => x.addEventListener("change", checkSelectedAmtFollowing));
selectboxes = Array.from(el_id_listfollowing.querySelectorAll(".following_checkbox"));
checkSelectedAmtFollowing();
}
@ -296,15 +386,17 @@ el_id_selectnofollowing.addEventListener("click", () => {
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_submitfollowing.addEventListener("click", () => {
console.log(selectedUsers);
selectedUsers = [];
selectboxes.forEach(({checked}, i) => {
if (checked) selectedUsers.push(userList.at(i));
});
if (selectedUsers.length < 2) {
selectedUsers = [];
el_id_loadingfollowing.innerHTML = `Selected user count is less than 2! (${selectboxes.filter(x => x.checked).length} selected)`;
return;
}
@ -315,6 +407,122 @@ function checkSelectedAmtFollowing() {
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
function shuf(array) {
@ -341,14 +549,14 @@ const el_id_gameUserOne = document.querySelector("#gameUserOne");
const el_id_gameUserOneImage = document.querySelector("#gameUserOneImage");
const el_id_gameUserOneName = document.querySelector("#gameUserOneName");
const el_id_gameUserOneId = document.querySelector("#gameUserOneId");
const el_id_gameUserOneNote = document.querySelector("#gameUserOneNote");
const el_id_gameUserOneFields = document.querySelector("#gameUserOneFields");
// const el_id_gameUserOneNote = document.querySelector("#gameUserOneNote");
// const el_id_gameUserOneFields = document.querySelector("#gameUserOneFields");
const el_id_gameUserTwo = document.querySelector("#gameUserTwo");
const el_id_gameUserTwoImage = document.querySelector("#gameUserTwoImage");
const el_id_gameUserTwoName = document.querySelector("#gameUserTwoName");
const el_id_gameUserTwoId = document.querySelector("#gameUserTwoId");
const el_id_gameUserTwoNote = document.querySelector("#gameUserTwoNote");
const el_id_gameUserTwoFields = document.querySelector("#gameUserTwoFields");
// const el_id_gameUserTwoNote = document.querySelector("#gameUserTwoNote");
// const el_id_gameUserTwoFields = document.querySelector("#gameUserTwoFields");
const el_id_gameSubmitLeft = document.querySelector("#gameSubmitLeft");
const el_id_gameSubmitRight = document.querySelector("#gameSubmitRight");
const el_id_gameBracketLevel = document.querySelector("#gameBracketLevel");
@ -359,19 +567,16 @@ let curDepth = 0;
let curFight = 0;
function sortSelectedUsers() {
if (gamemodeFollowers)
el_id_followers.hidden = true;
else
el_id_following.hidden = true;
el_id_game.hidden = false;
goToState("game");
state = [];
curDepth = 0;
curFight = 0;
state.push(chunk(shuf(selectedUsers), 2));
state.push([]);
prepareGameStage();
}
function prepareGameStage() {
el_id_gameSubmitLeft.disabled = true;
el_id_gameSubmitRight.disabled = true;
el_id_gameSubmitLeft.innerHTML = "Please wait...";
el_id_gameSubmitRight.innerHTML = "Please wait...";
el_id_gameUserOneImage.src = "";
@ -380,15 +585,15 @@ function prepareGameStage() {
el_id_gameBracketFight.innerHTML = "Round " + (curFight + 1);
const curSubStage = state[curDepth][curFight];
el_id_gameUserOneImage.src = curSubStage[0].avatar;
el_id_gameUserOneName.innerHTML = renderNameHTML(escapeHtml(curSubStage[0].display_name), curSubStage[0]);
el_id_gameUserOneName.innerHTML = `<a href="${curSubStage[0].url}" target="_blank">${renderNameHTML(escapeHtml(curSubStage[0].display_name), curSubStage[0])}</a>`;
el_id_gameUserOneId.innerHTML = "@" + curSubStage[0].fqn;
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_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_gameUserTwoImage.src = curSubStage[1].avatar;
el_id_gameUserTwoName.innerHTML = renderNameHTML(escapeHtml(curSubStage[1].display_name), curSubStage[1]);
el_id_gameUserTwoName.innerHTML = `<a href="${curSubStage[1].url}" target="_blank">${renderNameHTML(escapeHtml(curSubStage[1].display_name), curSubStage[1])}</a>`;
el_id_gameUserTwoId.innerHTML = "@" + curSubStage[1].fqn;
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_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_gameSubmitLeft.disabled = false;
el_id_gameSubmitRight.disabled = false;
el_id_gameSubmitLeft.innerHTML = "Pick " + "@" + curSubStage[0].fqn;
@ -437,44 +642,94 @@ const el_id_winnerUser = document.querySelector("#winnerUser");
const el_id_winnerUserImage = document.querySelector("#winnerUserImage");
const el_id_winnerUserName = document.querySelector("#winnerUserName");
const el_id_winnerUserId = document.querySelector("#winnerUserId");
const el_id_winnerUserNote = document.querySelector("#winnerUserNote");
const el_id_winnerUserFields = document.querySelector("#winnerUserFields");
// const el_id_winnerUserNote = document.querySelector("#winnerUserNote");
// const el_id_winnerUserFields = document.querySelector("#winnerUserFields");
const el_id_winnermessage = document.querySelector("#winnermessage");
const el_id_winnerusers = document.querySelector("#winnerusers");
const el_id_winnerPlayAgain = document.querySelector("#winnerPlayAgain");
function endGame() {
el_id_game.hidden = true;
el_id_winner.hidden = false;
goToState("winner");
const winningPlayer = state[curDepth][curFight][0];
el_id_winnerUserImage.src = winningPlayer.avatar;
el_id_winnerUserName.innerHTML = renderNameHTML(escapeHtml(winningPlayer.display_name), winningPlayer);
el_id_winnerUserName.innerHTML = `<a href="${winningPlayer.url}" target="_blank">${renderNameHTML(escapeHtml(winningPlayer.display_name), winningPlayer)}</a>`;
el_id_winnerUserId.innerHTML = "@" + winningPlayer.fqn;
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_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_winnermessage.innerHTML = `Congratulations, ${renderNameHTML(escapeHtml(winningPlayer.display_name), winningPlayer)}! You won the bracket!`;
el_id_winnerusers.innerHTML = `<div>${state.reduce((pv, cs) => {
el_id_winnerusers.innerHTML = state.reduce((pv, cs) => {
return pv + `<div class="flexUser">${cs.reduce((pvus, cus) => {
return pvus + cus.reduce((pvu, cu) => {
return pvu + `<div class="flexUserUser">
<img src="${cu.avatar}" width="64" height="64" />
<h3>${renderNameHTML(escapeHtml(cu.display_name), cu)}</h3>
<p>@${cu.fqn}</p>
</div>`
}, "")
console.log(cus);
return pvus + `<div class="flexUserGroup"><div class="flexUserUser">
<img src="${cus[0].avatar}" width="64" height="64" />
<h3><a href="${cus[0].url}" target="_blank">${renderNameHTML(escapeHtml(cus[0].display_name), cus[0])}</a></h3>
<p>@${cus[0].fqn}</p>
</div>${cus[1] ? `<div class="flexUserUser">
<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>`
}, "");
}
el_id_winnerPlayAgain.addEventListener("click", (x) => {
el_id_winner.hidden = true;
state = [];
selectedUsers = [];
curDepth = 0;
curFight = 0;
if (gamemodeFollowers)
getFollowers(true);
else
getFollowing(true);
})
switch (gamemodeFollowers) {
case "followers":
startFollowers(false);
break;
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 {
display: flex;
flex-direction: column;
align-items: safe center;
flex-direction: row;
align-items: stretch;
overflow: auto;
gap: 0.5em;
}
.flexUser {
display: flex;
flex-direction: column;
flex-wrap: nowrap;
justify-content: space-evenly;
width: 100%;
justify-content: space-around;
gap: 0.5em;
}
@ -71,10 +71,21 @@
text-align: center;
width: 256px;
min-width: 256px;
height: 150px;
min-height: 150px;
box-sizing: content-box;
overflow: hidden;
word-wrap: break-word;
}
.flexUserGroup {
display: flex;
flex-direction: column;
flex-wrap: nowrap;
justify-content: space-around;
gap: 0.5em;
}
.flexUserUser * {
word-wrap: break-word;
}

96
views/robots.txt Normal file
View file

@ -0,0 +1,96 @@
# 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