"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var friends_exports = {};
__export(friends_exports, {
Friends: () => Friends,
commands: () => commands,
handlers: () => handlers,
loginfilter: () => loginfilter,
pages: () => pages
});
module.exports = __toCommonJS(friends_exports);
var import_utils = require("../../lib/utils");
var import_friends = require("../friends");
const STATUS_COLORS = {
idle: "#ff7000",
online: "#009900",
busy: "#cc3838"
};
const STATUS_TITLES = {
online: "Online",
idle: "Idle",
busy: "Busy",
offline: "Offline"
};
const LOGIN_NOTIFY_THROTTLE = 15 * 60 * 1e3;
const Friends = new class {
async notifyPending(user) {
if (user.settings.blockFriendRequests)
return;
const friendRequests = await Chat.Friends.getRequests(user);
const pendingCount = friendRequests.received.size;
if (pendingCount < 1)
return;
if (pendingCount === 1) {
const sender = [...friendRequests.received][0];
const senderName = Users.getExact(sender)?.name || sender;
let buf = import_utils.Utils.html`/uhtml sent,Accept | `;
buf += import_utils.Utils.html`Deny `;
buf += `(You can also stop this user from sending you friend requests with /ignore
) `;
(0, import_friends.sendPM)(import_utils.Utils.html`/raw ${senderName} sent you a friend request!`, user.id);
(0, import_friends.sendPM)(buf, user.id);
(0, import_friends.sendPM)(
`/raw Note: If this request is accepted, your friend will be notified when you come online, and you will be notified when they do, unless you opt out of receiving them. `,
user.id
);
} else {
(0, import_friends.sendPM)(`/nonotify You have ${pendingCount} friend requests pending!`, user.id);
(0, import_friends.sendPM)(`/raw View `, user.id);
}
}
async notifyConnection(user) {
const connected = await Chat.Friends.getLastLogin(user.id);
if (connected && Date.now() - connected < LOGIN_NOTIFY_THROTTLE) {
return;
}
const friends = await Chat.Friends.getFriends(user.id);
const message = `/nonotify Your friend ${import_utils.Utils.escapeHTML(user.name)} has just connected!`;
for (const f of friends) {
const curUser = Users.getExact(f.friend);
if (curUser?.settings.allowFriendNotifications) {
curUser.send(`|pm|~|${curUser.getIdentity()}|${message}`);
}
}
}
async visualizeList(userid) {
const friends = await Chat.Friends.getFriends(userid);
const categorized = {
online: [],
idle: [],
busy: [],
offline: []
};
const loginTimes = {};
for (const { friend: friendID, last_login, allowing_login: hideLogin } of [...friends].sort()) {
const friend = Users.getExact(friendID);
if (friend?.connected) {
categorized[friend.statusType].push(friend.id);
} else {
categorized.offline.push(friendID);
if (!hideLogin) {
loginTimes[toID(friendID)] = last_login;
}
}
}
const sorted = Object.keys(categorized).filter((item) => categorized[item].length > 0).map((item) => `${STATUS_TITLES[item]} (${categorized[item].length})`);
let buf = `
Your friends: `;
if (sorted.length > 0) {
buf += ` Total (${friends.length}) | ${sorted.join(" | ")} `;
} else {
buf += `you have no friends added on Showdown lol `;
buf += `To add a friend, use /friend add [username]
. `;
return buf;
}
buf += ``;
for (const key in categorized) {
const friendArray = categorized[key].sort();
if (friendArray.length === 0)
continue;
buf += `${STATUS_TITLES[key]} (${friendArray.length}) `;
for (const friend of friendArray) {
const friendID = toID(friend);
buf += ``;
buf += this.displayFriend(friendID, loginTimes[friendID]);
buf += `
`;
}
}
return buf;
}
// much more info redacted
async visualizePublicList(userid) {
const friends = (await Chat.Friends.getFriends(userid)).map((f) => f.friend);
let buf = `${userid}'s friends: `;
if (!friends.length) {
buf += `None.`;
return buf;
}
for (const friend of friends) {
buf += `- ${friend} `;
}
return buf;
}
displayFriend(userid, login) {
const user = Users.getExact(userid);
const name = import_utils.Utils.escapeHTML(user ? user.name : userid);
const statusType = user?.connected ? `\u25C9 ${STATUS_TITLES[user.statusType]} ` : "\u25CC Offline";
let buf = user ? ` ${name} (${statusType}) ` : import_utils.Utils.html`${name} (${statusType}) `;
buf += ` `;
const curUser = Users.get(userid);
if (user) {
if (user.userMessage)
buf += import_utils.Utils.html`Status: ${user.userMessage} `;
} else if (curUser && curUser.id !== userid) {
buf += `On an alternate account `;
}
if (login && typeof login === "number" && !user?.connected) {
buf += `Last seen: ${new Date(Number(login)).toISOString()} `;
buf += ` (${Chat.toDurationString(Date.now() - login, { precision: 1 })} ago)`;
} else if (typeof login === "string") {
buf += `${login}`;
}
buf = `${buf}
`;
return toLink(buf);
}
checkCanUse(context) {
const user = context.user;
if (!user.autoconfirmed) {
throw new Chat.ErrorMessage(context.tr`You must be autoconfirmed to use the friends feature.`);
}
if (user.locked || user.namelocked || user.semilocked || user.permalocked) {
throw new Chat.ErrorMessage(`You are locked, and so cannot use the friends feature.`);
}
if (!Config.usesqlitefriends || !Config.usesqlite) {
throw new Chat.ErrorMessage(`The friends list feature is currently disabled.`);
}
if (!Users.globalAuth.atLeast(user, Config.usesqlitefriends)) {
throw new Chat.ErrorMessage(`You are currently unable to use the friends feature.`);
}
}
request(user, receiver) {
return Chat.Friends.request(user, receiver);
}
removeFriend(userid, friendID) {
return Chat.Friends.removeFriend(userid, friendID);
}
approveRequest(receiverID, senderID) {
return Chat.Friends.approveRequest(receiverID, senderID);
}
removeRequest(receiverID, senderID) {
return Chat.Friends.removeRequest(receiverID, senderID);
}
updateSpectatorLists(user) {
if (!user.friends)
return;
for (const id of user.friends) {
const curUser = Users.getExact(id);
if (curUser) {
for (const conn of curUser.connections) {
if (conn.openPages?.has("friends-spectate")) {
void Chat.parse("/friends view spectate", null, curUser, conn);
}
}
}
}
}
}();
function toLink(buf) {
return buf.replace(/',
received: ' ',
all: ' ',
help: ' ',
settings: ' ',
spectate: ' '
};
const titles = {
all: "All Friends",
spectate: "Spectate",
sent: "Sent",
received: "Received",
help: "Help",
settings: "Settings"
};
for (const page in titles) {
const title = titles[page];
const icon = icons[page];
if (page === type) {
buf.push(`${icon} ${user.tr(title)} `);
} else {
buf.push(`${icon} ${user.tr(title)} `);
}
}
const refresh = ` ${user.tr("Refresh")} `;
return `${buf.join(" / ")}${refresh}
`;
}
const commands = {
unfriend(target) {
return this.parse(`/friend remove ${target}`);
},
friend: "friends",
friendslist: "friends",
friends: {
""(target) {
if (toID(target)) {
return this.parse(`/friend add ${target}`);
}
return this.parse(`/friends list`);
},
viewlist(target, room, user) {
Friends.checkCanUse(this);
target = toID(target);
if (!target)
return this.errorReply(`Specify a user.`);
if (target === user.id)
return this.parse(`/friends list`);
return this.parse(`/j view-friends-viewuser-${target}`);
},
request: "add",
async add(target, room, user, connection) {
Friends.checkCanUse(this);
target = toID(target);
if (target.length > 18) {
return this.errorReply(this.tr`That name is too long - choose a valid name.`);
}
if (!target)
return this.parse("/help friends");
await Friends.request(user, target);
this.refreshPage("friends-sent");
return this.sendReply(`You sent a friend request to '${target}'.`);
},
unfriend: "remove",
async remove(target, room, user) {
Friends.checkCanUse(this);
target = toID(target);
if (!target)
return this.parse("/help friends");
await Friends.removeFriend(user.id, target);
this.sendReply(`Removed friend '${target}'.`);
await Chat.Friends.updateUserCache(user);
this.refreshPage("friends-all");
const targetUser = Users.get(target);
if (targetUser)
await Chat.Friends.updateUserCache(targetUser);
},
view(target) {
return this.parse(`/join view-friends-${target}`);
},
list() {
return this.parse(`/join view-friends-all`);
},
async accept(target, room, user, connection) {
Friends.checkCanUse(this);
target = toID(target);
if (user.settings.blockFriendRequests) {
return this.errorReply(this.tr`You are currently blocking friend requests, and so cannot accept your own.`);
}
if (!target)
return this.parse("/help friends");
await Friends.approveRequest(user.id, target);
const targetUser = Users.get(target);
(0, import_friends.sendPM)(`You accepted a friend request from "${target}".`, user.id);
this.refreshPage("friends-received");
if (targetUser) {
(0, import_friends.sendPM)(`/text ${user.name} accepted your friend request!`, targetUser.id);
(0, import_friends.sendPM)(`/uhtmlchange sent-${targetUser.id},`, targetUser.id);
(0, import_friends.sendPM)(`/uhtmlchange undo-${targetUser.id},`, targetUser.id);
}
await Chat.Friends.updateUserCache(user);
if (targetUser)
await Chat.Friends.updateUserCache(targetUser);
},
deny: "reject",
async reject(target, room, user, connection) {
Friends.checkCanUse(this);
target = toID(target);
if (!target)
return this.parse("/help friends");
const res = await Friends.removeRequest(user.id, target);
if (!res.changes) {
return this.errorReply(`You do not have a friend request pending from '${target}'.`);
}
this.refreshPage("friends-received");
return (0, import_friends.sendPM)(`You denied a friend request from '${target}'.`, user.id);
},
toggle(target, room, user, connection) {
Friends.checkCanUse(this);
const setting = user.settings.blockFriendRequests;
target = target.trim();
if (this.meansYes(target)) {
if (!setting)
return this.errorReply(this.tr`You already are allowing friend requests.`);
user.settings.blockFriendRequests = false;
this.sendReply(this.tr`You are now allowing friend requests.`);
} else if (this.meansNo(target)) {
if (setting)
return this.errorReply(this.tr`You already are blocking incoming friend requests.`);
user.settings.blockFriendRequests = true;
this.sendReply(this.tr`You are now blocking incoming friend requests.`);
} else {
if (target)
this.errorReply(this.tr`Unrecognized setting.`);
this.sendReply(
this.tr(setting ? `You are currently blocking friend requests.` : `You are not blocking friend requests.`)
);
}
this.refreshPage("friends-settings");
user.update();
},
async undorequest(target, room, user, connection) {
Friends.checkCanUse(this);
target = toID(target);
await Friends.removeRequest(target, user.id);
this.refreshPage("friends-sent");
return (0, import_friends.sendPM)(`You removed your friend request to '${target}'.`, user.id);
},
hidenotifs: "viewnotifications",
hidenotifications: "viewnotifications",
viewnotifs: "viewnotifications",
viewnotifications(target, room, user, connection, cmd) {
const setting = user.settings.allowFriendNotifications;
target = target.trim();
if (!cmd.includes("hide") || target && this.meansYes(target)) {
if (setting)
return this.errorReply(this.tr(`You are already allowing friend notifications.`));
user.settings.allowFriendNotifications = true;
this.sendReply(this.tr(`You will now receive friend notifications.`));
} else if (cmd.includes("hide") || target && this.meansNo(target)) {
if (!setting)
return this.errorReply(this.tr`You are already not receiving friend notifications.`);
user.settings.allowFriendNotifications = false;
this.sendReply(this.tr`You will not receive friend notifications.`);
} else {
if (target)
this.errorReply(this.tr`Unrecognized setting.`);
this.sendReply(
this.tr(setting ? `You are currently allowing friend notifications.` : `Your friend notifications are disabled.`)
);
}
this.refreshPage("friends-settings");
user.update();
},
hidelogins: "togglelogins",
showlogins: "togglelogins",
async togglelogins(target, room, user, connection, cmd) {
Friends.checkCanUse(this);
const setting = user.settings.hideLogins;
if (cmd.includes("hide")) {
if (setting)
return this.errorReply(this.tr`You are already hiding your logins from friends.`);
user.settings.hideLogins = true;
await Chat.Friends.hideLoginData(user.id);
this.sendReply(`You are now hiding your login times from your friends.`);
} else if (cmd.includes("show")) {
if (!setting)
return this.errorReply(this.tr`You are already allowing friends to see your login times.`);
user.settings.hideLogins = false;
await Chat.Friends.allowLoginData(user.id);
this.sendReply(`You are now allowing your friends to see your login times.`);
} else {
return this.errorReply(`Invalid setting.`);
}
this.refreshPage("friends-settings");
user.update();
},
async listdisplay(target, room, user, connection) {
Friends.checkCanUse(this);
target = toID(target);
const { public_list: setting } = await Chat.Friends.getSettings(user.id);
if (this.meansYes(target)) {
if (setting) {
return this.errorReply(this.tr`You are already allowing other people to view your friends list.`);
}
await Chat.Friends.setHideList(user.id, true);
this.refreshPage("friends-settings");
return this.sendReply(this.tr`You are now allowing other people to view your friends list.`);
} else if (this.meansNo(target)) {
if (!setting) {
return this.errorReply(this.tr`You are already hiding your friends list.`);
}
await Chat.Friends.setHideList(user.id, false);
this.refreshPage("friends-settings");
return this.sendReply(this.tr`You are now hiding your friends list.`);
}
this.sendReply(`You are currently ${setting ? "displaying" : "hiding"} your friends list.`);
},
invalidatecache(target, room, user) {
this.canUseConsole();
for (const curUser of Users.users.values()) {
void Chat.Friends.updateUserCache(curUser);
}
Rooms.global.notifyRooms(
["staff", "development"],
`|c|${user.getIdentity()}|/log ${user.name} used /friends invalidatecache`
);
this.sendReply(`You invalidated each entry in the friends database cache.`);
},
sharebattles(target, room, user) {
Friends.checkCanUse(this);
target = toID(target);
if (this.meansYes(target)) {
if (user.settings.displayBattlesToFriends) {
return this.errorReply(this.tr`You are already sharing your battles with friends.`);
}
user.settings.displayBattlesToFriends = true;
this.sendReply(`You are now allowing your friends to see your ongoing battles.`);
} else if (this.meansNo(target)) {
if (!user.settings.displayBattlesToFriends) {
return this.errorReply(this.tr`You are already not sharing your battles with friends.`);
}
user.settings.displayBattlesToFriends = false;
this.sendReply(`You are now hiding your ongoing battles from your friends.`);
} else {
if (!target)
return this.parse("/help friends sharebattles");
return this.errorReply(`Invalid setting '${target}'. Provide 'on' or 'off'.`);
}
user.update();
this.refreshPage("friends-settings");
},
sharebattleshelp: [
`/friends sharebattles [on|off] - Allow or disallow your friends from seeing your ongoing battles.`
]
},
friendshelp() {
this.runBroadcast();
if (this.broadcasting) {
return this.sendReplyBox([
`/friend list
- View current friends.`,
`/friend add [name]
OR /friend [name]
- Send a friend request to [name], if you don't have them added.`,
`/friend remove [username]
OR /unfriend [username]
- Unfriend the user.`,
`More commands... `,
`/friend accept [username]
- Accepts the friend request from [username], if it exists.`,
`/friend reject [username]
- Rejects the friend request from [username], if it exists.`,
`/friend toggle [off/on]
- Enable or disable receiving of friend requests.`,
`/friend hidenotifications
OR hidenotifs
- Opts out of receiving friend notifications.`,
`/friend viewnotifications
OR viewnotifs
- Opts into view friend notifications.`,
`/friend listdisplay [on/off]
- Opts [in/out] of letting others view your friends list.`,
`/friend viewlist [user]
- View the given [user]'s friend list, if they're allowing others to see.`,
`/friends sharebattles [on|off]
- Allow or disallow your friends from seeing your ongoing battles. `
].join(" "));
}
return this.parse("/join view-friends-help");
}
};
const pages = {
async friends(args, user) {
if (!user.named)
return Rooms.RETRY_AFTER_LOGIN;
Friends.checkCanUse(this);
const type = args.shift();
let buf = '';
switch (toID(type)) {
case "outgoing":
case "sent":
this.title = `[Friends] Sent`;
buf += headerButtons("sent", user);
if (user.settings.blockFriendRequests) {
buf += `
${this.tr(`You are currently blocking friend requests`)}. `;
}
const { sent } = await Chat.Friends.getRequests(user);
if (sent.size < 1) {
buf += `You have no outgoing friend requests pending. `;
buf += ` To add a friend, use /friend add [username]
.`;
buf += ``;
return toLink(buf);
}
buf += `You have ${Chat.count(sent.size, "friend requests")} pending${sent.size === import_friends.MAX_REQUESTS ? ` (maximum reached)` : ""}. `;
for (const request of sent) {
buf += ``;
buf += `${request} `;
buf += ` `;
buf += ` ${this.tr("Undo")} `;
buf += `
`;
}
break;
case "received":
case "incoming":
this.title = `[Friends] Received`;
buf += headerButtons("received", user);
const { received } = await Chat.Friends.getRequests(user);
if (received.size < 1) {
buf += `You have no pending friend requests. `;
buf += ``;
return toLink(buf);
}
buf += `You have ${received.size} pending friend requests. `;
for (const request of received) {
buf += ``;
buf += `${request} `;
buf += ` ${this.tr("Accept")} |`;
buf += ` ${this.tr("Deny")} `;
buf += `
`;
}
break;
case "viewuser":
const target = toID(args.shift());
if (!target)
return this.errorReply(`Specify a user.`);
if (target === user.id) {
return this.errorReply(`Use /friends list to view your own list.`);
}
const { public_list: isAllowing } = await Chat.Friends.getSettings(target);
if (!isAllowing)
return this.errorReply(`${target}'s friends list is not public or they do not have one.`);
this.title = `[Friends List] ${target}`;
buf += await Friends.visualizePublicList(target);
break;
case "help":
this.title = `[Friends] Help`;
buf += headerButtons("help", user);
buf += `Help `;
buf += `/friend OR /friends OR /friendslist: `;
buf += [
`/friend list
- View current friends.`,
`/friend add [name]
OR /friend [name]
- Send a friend request to [name], if you don't have them added.`,
`/friend remove [username]
OR /unfriend [username]
- Unfriend the user.`,
`/friend accept [username]
- Accepts the friend request from [username], if it exists.`,
`/friend reject [username]
- Rejects the friend request from [username], if it exists.`,
`/friend toggle [off/on]
- Enable or disable receiving of friend requests.`,
`/friend hidenotifications
OR hidenotifs
- Opts out of receiving friend notifications.`,
`/friend viewnotifications
OR viewnotifs
- Opts into view friend notifications.`,
`/friend listdisplay [on/off]
- Opts [in/out] of letting others view your friends list.`,
`/friend viewlist [user]
- View the given [user]'s friend list, if they're allowing others to see.`,
`/friends sharebattles [on|off]
- Allow or disallow your friends from seeing your ongoing battles.`
].join(" ");
buf += ` `;
break;
case "settings":
this.title = `[Friends] Settings`;
buf += headerButtons("settings", user);
buf += `Friends Settings: `;
const settings = user.settings;
const { public_list, send_login_data } = await Chat.Friends.getSettings(user.id);
buf += `Notify me when my friends come online: `;
buf += `Disable `;
buf += `Enable `;
buf += `Receive friend requests: `;
buf += `Disable `;
buf += `Enable `;
buf += `Allow others to see your list: `;
buf += `Allow `;
buf += `Hide `;
buf += `Allow others to see my login times `;
buf += `Disable `;
buf += `Enable `;
buf += `Allow friends to see my hidden battles on the spectator list: `;
buf += `Disable `;
buf += `Enable `;
buf += `Block PMs except from friends (and staff): `;
buf += `Disable `;
buf += `Enable `;
buf += `Block challenges except from friends (and staff): `;
buf += `Disable `;
buf += `Enable `;
break;
case "spectate":
this.title = `[Friends] Spectating`;
buf += headerButtons("spectate", user);
buf += `Spectate your friends: `;
const toggleMessage = user.settings.displayBattlesToFriends ? " disallow your friends from seeing your hidden battles" : " allow your friends to see your hidden battles";
buf += `Use the settings page to ${toggleMessage} on this page. `;
buf += ` `;
if (!user.friends?.size) {
buf += `You have no friends to spectate. `;
break;
}
const friends = [];
for (const friendID of user.friends) {
const friend = Users.getExact(friendID);
if (!friend)
continue;
friends.push(friend);
}
if (!friends.length) {
buf += `None of your friends are currently around to spectate. `;
break;
}
const battles = [];
for (const friend of friends) {
const curBattles = [...friend.inRooms].filter((id) => {
const battle = Rooms.get(id)?.battle;
return battle?.playerTable[friend.id] && (!battle.roomid.endsWith("pw") || friend.settings.displayBattlesToFriends);
}).map((id) => [friend, id]);
if (!curBattles.length)
continue;
battles.push(...curBattles);
}
import_utils.Utils.sortBy(battles, ([, id]) => -Number(id.split("-")[2]));
if (!battles.length) {
buf += `None of your friends are currently in a battle. `;
} else {
buf += battles.map(([friend, battle]) => {
const room = Rooms.get(battle);
const format = Dex.formats.get(room.battle.format).name;
const rated = room.battle.rated ? `(Rated: ${room.battle.rated}) ` : "";
const title = room.title.includes(friend.name) ? room.title.replace(friend.name, `${friend.name} `) : room.title + ` (with ${friend.name})`;
return `[${format}] ${rated} ${title} `;
}).join(" ");
}
break;
default:
this.title = `[Friends] All Friends`;
buf += headerButtons("all", user);
buf += await Friends.visualizeList(user.id);
}
buf += ``;
return toLink(buf);
}
};
const handlers = {
onBattleStart(user) {
return Friends.updateSpectatorLists(user);
},
onBattleLeave(user, room) {
return Friends.updateSpectatorLists(user);
},
onBattleEnd(battle, winner, players) {
for (const id of players) {
const user = Users.get(id);
if (!user)
continue;
Friends.updateSpectatorLists(user);
}
},
onDisconnect(user) {
void Chat.Friends.writeLogin(user.id);
}
};
const loginfilter = (user) => {
if (!Config.usesqlitefriends || !Users.globalAuth.atLeast(user, Config.usesqlitefriends)) {
return;
}
void Friends.notifyPending(user);
void Friends.notifyConnection(user);
void Chat.Friends.writeLogin(user.id);
void Chat.Friends.updateUserCache(user);
};
//# sourceMappingURL=friends.js.map