Added ability to opt out of leaderboards (#4016)

* added button to opt out of leaderboards

* also not allowing daily leaderboards

* also not allowing weekly lb

* added lb opt out notice on the leaderboards

* updated message

* resetting lboptout state on account reset
This commit is contained in:
Jack 2023-02-25 23:26:06 +01:00 committed by GitHub
parent 52b731d607
commit 56d8c7c9ae
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 205 additions and 10 deletions

View file

@ -283,7 +283,8 @@ export async function addResult(
result.mode === "time" &&
result.wpm > 130 &&
result.testDuration < 122 &&
(user.verified === false || user.verified === undefined)
(user.verified === false || user.verified === undefined) &&
user.lbOptOut !== true
) {
if (!result.keySpacingStats || !result.keyDurationStats) {
const status = MonkeyStatusCodes.MISSING_KEY_DATA;
@ -389,7 +390,8 @@ export async function addResult(
const validResultCriteria =
(funbox === "none" || funbox === "plus_one" || funbox === "plus_two") &&
!bailedOut &&
!user.banned &&
user.banned !== true &&
user.lbOptOut !== true &&
(process.env.MODE === "dev" || (user.timeTyping ?? 0) > 7200);
const selectedBadgeId = user.inventory?.badges?.find((b) => b.selected)?.id;
@ -438,7 +440,8 @@ export async function addResult(
const weeklyXpLeaderboardConfig = req.ctx.configuration.leaderboards.weeklyXp;
let weeklyXpLeaderboardRank = -1;
const eligibleForWeeklyXpLeaderboard =
!user.banned &&
user.banned !== true &&
user.lbOptOut !== true &&
(process.env.MODE === "dev" || (user.timeTyping ?? 0) > 7200);
const weeklyXpLeaderboard = WeeklyXpLeaderboard.get(

View file

@ -201,6 +201,21 @@ export async function clearPb(
return new MonkeyResponse("User's PB cleared");
}
export async function optOutOfLeaderboards(
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
await UserDAL.optOutOfLeaderboards(uid);
await purgeUserFromDailyLeaderboards(
uid,
req.ctx.configuration.dailyLeaderboards
);
Logger.logToDb("user_opted_out_of_leaderboards", "", uid);
return new MonkeyResponse("User opted out of leaderboards");
}
export async function checkName(
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
@ -606,6 +621,7 @@ export async function getProfile(
discordAvatar,
xp,
streak,
lbOptOut,
} = user;
const validTimePbs = _.pick(personalBests?.time, "15", "30", "60", "120");
@ -633,6 +649,7 @@ export async function getProfile(
xp,
streak: streak?.length ?? 0,
maxStreak: streak?.maxLength ?? 0,
lbOptOut,
};
if (banned) {

View file

@ -202,6 +202,15 @@ router.delete(
asyncHandler(UserController.clearPb)
);
router.post(
"/optOutOfLeaderboards",
authenticateRequest({
requireFreshToken: true,
}),
RateLimit.userOptOutOfLeaderboards,
asyncHandler(UserController.optOutOfLeaderboards)
);
const requireFilterPresetsEnabled = validateConfiguration({
criteria: (configuration) => {
return configuration.results.filterPresets.enabled;

View file

@ -79,6 +79,7 @@ export async function resetUser(uid: string): Promise<void> {
$unset: {
discordAvatar: "",
discordId: "",
lbOptOut: "",
},
}
);
@ -136,6 +137,20 @@ export async function clearPb(uid: string): Promise<void> {
);
}
export async function optOutOfLeaderboards(uid: string): Promise<void> {
await getUsersCollection().updateOne(
{ uid },
{
$set: {
lbOptOut: true,
lbPersonalBests: {
time: {},
},
},
}
);
}
export async function updateQuoteRatings(
uid: string,
quoteRatings: MonkeyTypes.UserQuoteRatings

View file

@ -348,6 +348,13 @@ export const userClearPB = rateLimit({
handler: customHandler,
});
export const userOptOutOfLeaderboards = rateLimit({
windowMs: ONE_HOUR_MS,
max: 10 * REQUEST_MULTIPLIER,
keyGenerator: getKeyWithUid,
handler: customHandler,
});
export const userCustomFilterAdd = rateLimit({
windowMs: ONE_HOUR_MS,
max: 60 * REQUEST_MULTIPLIER,

View file

@ -189,6 +189,7 @@ declare namespace MonkeyTypes {
inbox?: MonkeyMail[];
streak?: UserStreak;
lastReultHashes?: string[];
lbOptOut?: boolean;
}
interface UserStreak {

View file

@ -209,6 +209,9 @@
align-self: normal;
color: var(--text-color);
grid-area: text;
.red {
color: var(--error-color);
}
}
.inputs {

View file

@ -37,6 +37,10 @@ export default class Users {
return await this.httpClient.patch(`${BASE_PATH}/reset`);
}
async optOutOfLeaderboards(): Ape.EndpointData {
return await this.httpClient.post(`${BASE_PATH}/optOutOfLeaderboards`);
}
async updateName(name: string): Ape.EndpointData {
return await this.httpClient.patch(`${BASE_PATH}/name`, {
payload: { name },

View file

@ -18,6 +18,7 @@ export function setSnapshot(
): void {
const originalBanned = dbSnapshot?.banned;
const originalVerified = dbSnapshot?.verified;
const lbOptOut = dbSnapshot?.lbOptOut;
//not allowing user to override these values i guess?
try {
@ -26,10 +27,14 @@ export function setSnapshot(
try {
delete newSnapshot?.verified;
} catch {}
try {
delete newSnapshot?.lbOptOut;
} catch {}
dbSnapshot = newSnapshot;
if (dbSnapshot) {
dbSnapshot.banned = originalBanned;
dbSnapshot.verified = originalVerified;
dbSnapshot.lbOptOut = lbOptOut;
}
}
@ -91,6 +96,7 @@ export async function initSnapshot(): Promise<
snap.name = userData.name;
snap.personalBests = userData.personalBests;
snap.banned = userData.banned;
snap.lbOptOut = userData.lbOptOut;
snap.verified = userData.verified;
snap.discordId = userData.discordId;
snap.discordAvatar = userData.discordAvatar;

View file

@ -150,11 +150,20 @@ function updateFooter(lb: LbKey): void {
return;
}
$(`#leaderboardsWrapper table.${side} tfoot`).html(`
if (DB.getSnapshot()?.lbOptOut === true) {
$(`#leaderboardsWrapper table.${side} tfoot`).html(`
<tr>
<td colspan="6" style="text-align:center;">You have opted out of the leaderboards</>
</tr>
`);
return;
} else {
$(`#leaderboardsWrapper table.${side} tfoot`).html(`
<tr>
<td colspan="6" style="text-align:center;">Not qualified</>
</tr>
`);
}
let toppercent;
if (currentTimeRange === "allTime" && currentRank[lb]) {

View file

@ -31,6 +31,8 @@ export async function update(
const banned = profile.banned === true;
const lbOptOut = profile.lbOptOut === true;
if (!details || !profile || !profile.name || !profile.addedAt) return;
details.find(".placeholderAvatar").removeClass("hidden");
@ -75,6 +77,14 @@ export async function update(
);
}
if (lbOptOut) {
details
.find(".name")
.append(
`<div class="bannedIcon" aria-label="This account has opted out of leaderboards" data-balloon-pos="up"><i class="fas fa-crown"></i></div>`
);
}
updateNameFontSize(where);
const joinedText = "Joined " + format(profile.addedAt ?? 0, "dd MMM yyyy");

View file

@ -828,6 +828,10 @@ export function showAccountSection(): void {
refreshTagsSettingsSection();
refreshPresetsSettingsSection();
updateDiscordSection();
if (DB.getSnapshot()?.lbOptOut === true) {
$(".pageSettings .section.optOutOfLeaderboards").remove();
}
}
export async function update(groupUpdate = true): Promise<void> {

View file

@ -734,7 +734,7 @@ list["resetAccount"] = new SimplePopup(
Notifications.add("Resetting settings...", 0);
UpdateConfig.reset();
Loader.show();
Notifications.add("Resetting account and stats...", 0);
Notifications.add("Resetting account...", 0);
const response = await Ape.users.reset();
if (response.status !== 200) {
@ -772,6 +772,71 @@ list["resetAccount"] = new SimplePopup(
}
);
list["optOutOfLeaderboards"] = new SimplePopup(
"optOutOfLeaderboards",
"text",
"Opt out of leaderboards",
[
{
placeholder: "Password",
type: "password",
initVal: "",
},
],
"Are you sure you want to opt out of leaderboards?",
"Opt out",
async (_thisPopup, password: string) => {
try {
const user = Auth?.currentUser;
if (!user) return;
if (user.providerData.find((p) => p?.providerId === "password")) {
const credential = EmailAuthProvider.credential(
user.email as string,
password
);
await reauthenticateWithCredential(user, credential);
} else {
await reauthenticateWithPopup(user, AccountController.gmailProvider);
}
Loader.show();
const response = await Ape.users.optOutOfLeaderboards();
if (response.status !== 200) {
Loader.hide();
return Notifications.add(
`Failed to opt out of leaderboards: ${response.message}`,
-1
);
}
Loader.hide();
Notifications.add("Leaderboard opt out successful", 1);
setTimeout(() => {
location.reload();
}, 3000);
} catch (e) {
const typedError = e as FirebaseError;
Loader.hide();
if (typedError.code === "auth/wrong-password") {
Notifications.add("Incorrect password", -1);
} else {
Notifications.add("Something went wrong: " + e, -1);
}
}
},
(thisPopup) => {
const user = Auth?.currentUser;
if (!user) return;
if (!user.providerData.find((p) => p?.providerId === "password")) {
thisPopup.inputs = [];
thisPopup.buttonText = "Reauthenticate to reset";
}
},
(_thisPopup) => {
//
}
);
list["clearTagPb"] = new SimplePopup(
"clearTagPb",
"text",
@ -1376,6 +1441,14 @@ $(".pageSettings #resetAccount").on("click", () => {
list["resetAccount"].show();
});
$(".pageSettings #optOutOfLeaderboardsButton").on("click", () => {
if (!ConnectionState.get()) {
Notifications.add("You are offline", 0, 2);
return;
}
list["optOutOfLeaderboards"].show();
});
$("#popups").on("click", "#apeKeysPopup .generateApeKey", () => {
if (!ConnectionState.get()) {
Notifications.add("You are offline", 0, 2);

View file

@ -565,6 +565,7 @@ declare namespace MonkeyTypes {
inboxUnreadSize: number;
streak: number;
maxStreak: number;
lbOptOut?: boolean;
}
interface UserDetails {

View file

@ -2760,8 +2760,9 @@
<span>reset settings</span>
</div>
<div class="text">
Resets settings to the default (but doesn't touch your tags). Warning:
you can't undo this action!
Resets settings to the default (but doesn't touch your tags).
<br />
<span class="red">You can't undo this action!</span>
</div>
<div class="buttons">
<div
@ -2781,7 +2782,9 @@
</div>
<div class="text">
Resets all your personal bests (but doesn't delete any tests from your
history). Warning: you can't undo this action!
history).
<br />
<span class="red">You can't undo this action!</span>
</div>
<div class="buttons">
<div
@ -2794,12 +2797,38 @@
</div>
</div>
</div>
<div class="section optOutOfLeaderboards needsAccount hidden">
<div class="groupTitle">
<i class="fas fa-crown"></i>
<span>opt out of leaderboards</span>
</div>
<div class="text">
Use this if you frequently trigger the anticheat (for example if using
stenography) to opt out of leaderboards.
<br />
<span class="red">You can't undo this action!</span>
</div>
<div class="buttons">
<div
class="button danger"
id="optOutOfLeaderboardsButton"
tabindex="0"
onclick="this.blur();"
>
opt out
</div>
</div>
</div>
<div class="section resetAccount needsAccount hidden">
<div class="groupTitle">
<i class="fas fa-redo-alt"></i>
<span>reset account</span>
</div>
<div class="text">Completely resets your account to a blank state.</div>
<div class="text">
Completely resets your account to a blank state.
<br />
<span class="red">You can't undo this action!</span>
</div>
<div class="buttons">
<div
class="button danger"
@ -2816,7 +2845,11 @@
<i class="fas fa-trash"></i>
<span>delete account</span>
</div>
<div class="text">Deletes your account and all data connected to it.</div>
<div class="text">
Deletes your account and all data connected to it.
<br />
<span class="red">You can't undo this action!</span>
</div>
<div class="buttons">
<div
class="button danger"