{ "version": 3, "sources": ["../../../../server/chat-plugins/friends.ts"], "sourcesContent": ["/**\r\n * Friends list plugin.\r\n * Allows for adding and removing friends, as well as seeing their activity.\r\n * Written by Mia.\r\n * @author mia-pi-git\r\n */\r\n\r\nimport {Utils} from '../../lib/utils';\r\nimport {MAX_REQUESTS, sendPM} from '../friends';\r\n\r\nconst STATUS_COLORS: {[k: string]: string} = {\r\n\tidle: '#ff7000',\r\n\tonline: '#009900',\r\n\tbusy: '#cc3838',\r\n};\r\n\r\nconst STATUS_TITLES: {[k: string]: string} = {\r\n\tonline: 'Online',\r\n\tidle: 'Idle',\r\n\tbusy: 'Busy',\r\n\toffline: 'Offline',\r\n};\r\n\r\n// once every 15 minutes\r\nconst LOGIN_NOTIFY_THROTTLE = 15 * 60 * 1000;\r\n\r\nexport const Friends = new class {\r\n\tasync notifyPending(user: User) {\r\n\t\tif (user.settings.blockFriendRequests) return;\r\n\t\tconst friendRequests = await Chat.Friends.getRequests(user);\r\n\t\tconst pendingCount = friendRequests.received.size;\r\n\t\tif (pendingCount < 1) return;\r\n\t\tif (pendingCount === 1) {\r\n\t\t\tconst sender = [...friendRequests.received][0];\r\n\t\t\tconst senderName = Users.getExact(sender)?.name || sender;\r\n\t\t\tlet buf = Utils.html`/uhtml sent, | `;\r\n\t\t\tbuf += Utils.html`
`;\r\n\t\t\tbuf += `(You can also stop this user from sending you friend requests with /ignore)`;\r\n\t\t\tsendPM(Utils.html`/raw ${senderName} sent you a friend request!`, user.id);\r\n\t\t\tsendPM(buf, user.id);\r\n\t\t\tsendPM(\r\n\t\t\t\t`/raw Note: If this request is accepted, your friend will be notified when you come online, ` +\r\n\t\t\t\t`and you will be notified when they do, unless you opt out of receiving them.`,\r\n\t\t\t\tuser.id\r\n\t\t\t);\r\n\t\t} else {\r\n\t\t\tsendPM(`/nonotify You have ${pendingCount} friend requests pending!`, user.id);\r\n\t\t\tsendPM(`/raw `, user.id);\r\n\t\t}\r\n\t}\r\n\tasync notifyConnection(user: User) {\r\n\t\tconst connected = await Chat.Friends.getLastLogin(user.id);\r\n\t\tif (connected && (Date.now() - connected) < LOGIN_NOTIFY_THROTTLE) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tconst friends = await Chat.Friends.getFriends(user.id);\r\n\t\tconst message = `/nonotify Your friend ${Utils.escapeHTML(user.name)} has just connected!`;\r\n\t\tfor (const f of friends) {\r\n\t\t\tconst curUser = Users.getExact(f.friend);\r\n\t\t\tif (curUser?.settings.allowFriendNotifications) {\r\n\t\t\t\tcurUser.send(`|pm|&|${curUser.getIdentity()}|${message}`);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\tasync visualizeList(userid: ID) {\r\n\t\tconst friends = await Chat.Friends.getFriends(userid);\r\n\t\tconst categorized: {[k: string]: string[]} = {\r\n\t\t\tonline: [],\r\n\t\t\tidle: [],\r\n\t\t\tbusy: [],\r\n\t\t\toffline: [],\r\n\t\t};\r\n\t\tconst loginTimes: {[k: string]: number} = {};\r\n\t\tfor (const {friend: friendID, last_login, allowing_login: hideLogin} of [...friends].sort()) {\r\n\t\t\tconst friend = Users.getExact(friendID);\r\n\t\t\tif (friend?.connected) {\r\n\t\t\t\tcategorized[friend.statusType].push(friend.id);\r\n\t\t\t} else {\r\n\t\t\t\tcategorized.offline.push(friendID);\r\n\t\t\t\t// hidelogin - 1 to disable it being visible\r\n\t\t\t\tif (!hideLogin) {\r\n\t\t\t\t\tloginTimes[toID(friendID)] = last_login;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tconst sorted = Object.keys(categorized)\r\n\t\t\t.filter(item => categorized[item].length > 0)\r\n\t\t\t.map(item => `${STATUS_TITLES[item]} (${categorized[item].length})`);\r\n\r\n\t\tlet buf = `

Your friends: `;\r\n\t\tif (sorted.length > 0) {\r\n\t\t\tbuf += `Total (${friends.length}) | ${sorted.join(' | ')}`;\r\n\t\t} else {\r\n\t\t\tbuf += `

you have no friends added on Showdown lol


`;\r\n\t\t\tbuf += `To add a friend, use /friend add [username].

`;\r\n\t\t\treturn buf;\r\n\t\t}\r\n\t\tbuf += ` `;\r\n\r\n\t\tfor (const key in categorized) {\r\n\t\t\tconst friendArray = categorized[key].sort();\r\n\t\t\tif (friendArray.length === 0) continue;\r\n\t\t\tbuf += `

${STATUS_TITLES[key]} (${friendArray.length})

`;\r\n\t\t\tfor (const friend of friendArray) {\r\n\t\t\t\tconst friendID = toID(friend);\r\n\t\t\t\tbuf += `
`;\r\n\t\t\t\tbuf += this.displayFriend(friendID, loginTimes[friendID]);\r\n\t\t\t\tbuf += `
`;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn buf;\r\n\t}\r\n\t// much more info redacted\r\n\tasync visualizePublicList(userid: ID) {\r\n\t\tconst friends: string[] = (await Chat.Friends.getFriends(userid) as any[]).map(f => f.friend);\r\n\t\tlet buf = `

${userid}'s friends:


`;\r\n\t\tif (!friends.length) {\r\n\t\t\tbuf += `None.`;\r\n\t\t\treturn buf;\r\n\t\t}\r\n\t\tfor (const friend of friends) {\r\n\t\t\tbuf += `- ${friend}
`;\r\n\t\t}\r\n\t\treturn buf;\r\n\t}\r\n\tdisplayFriend(userid: ID, login?: number) {\r\n\t\tconst user = Users.getExact(userid); // we want this to be exact\r\n\t\tconst name = Utils.escapeHTML(user ? user.name : userid);\r\n\t\tconst statusType = user?.connected ?\r\n\t\t\t`\\u25C9 ${STATUS_TITLES[user.statusType]}` :\r\n\t\t\t'\\u25CC Offline';\r\n\t\tlet buf = user ?\r\n\t\t\t` ${name} (${statusType})` :\r\n\t\t\tUtils.html`${name} (${statusType})`;\r\n\t\tbuf += `
`;\r\n\r\n\t\tconst curUser = Users.get(userid); // might be an alt\r\n\t\tif (user) {\r\n\t\t\tif (user.userMessage) buf += Utils.html`Status: ${user.userMessage}
`;\r\n\t\t} else if (curUser && curUser.id !== userid) {\r\n\t\t\tbuf += `On an alternate account
`;\r\n\t\t}\r\n\t\tif (login && typeof login === 'number' && !user?.connected) {\r\n\t\t\t// THIS IS A TERRIBLE HACK BUT IT WORKS OKAY\r\n\t\t\tconst time = Chat.toTimestamp(new Date(Number(login)), {human: true});\r\n\t\t\tbuf += `Last seen: ${time.split(' ').reverse().join(', on ')}`;\r\n\t\t\tbuf += ` (${Chat.toDurationString(Date.now() - login, {precision: 1})} ago)`;\r\n\t\t} else if (typeof login === 'string') {\r\n\t\t\tbuf += `${login}`;\r\n\t\t}\r\n\t\tbuf = `
${buf}
`;\r\n\t\treturn toLink(buf);\r\n\t}\r\n\tcheckCanUse(context: Chat.CommandContext | Chat.PageContext) {\r\n\t\tconst user = context.user;\r\n\t\tif (!user.autoconfirmed) {\r\n\t\t\tthrow new Chat.ErrorMessage(context.tr`You must be autoconfirmed to use the friends feature.`);\r\n\t\t}\r\n\t\tif (user.locked || user.namelocked || user.semilocked || user.permalocked) {\r\n\t\t\tthrow new Chat.ErrorMessage(`You are locked, and so cannot use the friends feature.`);\r\n\t\t}\r\n\t\tif (!Config.usesqlitefriends || !Config.usesqlite) {\r\n\t\t\tthrow new Chat.ErrorMessage(`The friends list feature is currently disabled.`);\r\n\t\t}\r\n\t\tif (!Users.globalAuth.atLeast(user, Config.usesqlitefriends)) {\r\n\t\t\tthrow new Chat.ErrorMessage(`You are currently unable to use the friends feature.`);\r\n\t\t}\r\n\t}\r\n\trequest(user: User, receiver: ID) {\r\n\t\treturn Chat.Friends.request(user, receiver);\r\n\t}\r\n\tremoveFriend(userid: ID, friendID: ID) {\r\n\t\treturn Chat.Friends.removeFriend(userid, friendID);\r\n\t}\r\n\tapproveRequest(receiverID: ID, senderID: ID) {\r\n\t\treturn Chat.Friends.approveRequest(receiverID, senderID);\r\n\t}\r\n\tremoveRequest(receiverID: ID, senderID: ID) {\r\n\t\treturn Chat.Friends.removeRequest(receiverID, senderID);\r\n\t}\r\n\tupdateSpectatorLists(user: User) {\r\n\t\tif (!user.friends) return; // probably should never happen\r\n\t\tfor (const id of user.friends) {\r\n\t\t\t// should only work if theyre on that userid, since friends list is by userid\r\n\t\t\tconst curUser = Users.getExact(id);\r\n\t\t\tif (curUser) {\r\n\t\t\t\tfor (const conn of curUser.connections) {\r\n\t\t\t\t\tif (conn.openPages?.has('friends-spectate')) {\r\n\t\t\t\t\t\tvoid Chat.parse('/friends view spectate', null, curUser, conn);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/** UI functions chiefly for the chat page. */\r\n\r\nfunction toLink(buf: string) {\r\n\treturn buf.replace(/',\r\n\t\treceived: '',\r\n\t\tall: '',\r\n\t\thelp: '',\r\n\t\tsettings: '',\r\n\t\tspectate: '',\r\n\t};\r\n\tconst titles: {[k: string]: string} = {\r\n\t\tall: 'All Friends',\r\n\t\tspectate: 'Spectate',\r\n\t\tsent: 'Sent',\r\n\t\treceived: 'Received',\r\n\t\thelp: 'Help',\r\n\t\tsettings: 'Settings',\r\n\t};\r\n\tfor (const page in titles) {\r\n\t\tconst title = titles[page];\r\n\t\tconst icon = icons[page];\r\n\t\tif (page === type) {\r\n\t\t\tbuf.push(`${icon} ${user.tr(title)}`);\r\n\t\t} else {\r\n\t\t\tbuf.push(`${icon} ${user.tr(title)}`);\r\n\t\t}\r\n\t}\r\n\tconst refresh = (\r\n\t\t``\r\n\t);\r\n\treturn `
${buf.join(' / ')}${refresh}

`;\r\n}\r\n\r\nexport const commands: Chat.ChatCommands = {\r\n\tunfriend(target) {\r\n\t\treturn this.parse(`/friend remove ${target}`);\r\n\t},\r\n\tfriend: 'friends',\r\n\tfriendslist: 'friends',\r\n\tfriends: {\r\n\t\t''(target) {\r\n\t\t\tif (toID(target)) {\r\n\t\t\t\treturn this.parse(`/friend add ${target}`);\r\n\t\t\t}\r\n\t\t\treturn this.parse(`/friends list`);\r\n\t\t},\r\n\t\tviewlist(target, room, user) {\r\n\t\t\tFriends.checkCanUse(this);\r\n\t\t\ttarget = toID(target);\r\n\t\t\tif (!target) return this.errorReply(`Specify a user.`);\r\n\t\t\tif (target === user.id) return this.parse(`/friends list`);\r\n\t\t\treturn this.parse(`/j view-friends-viewuser-${target}`);\r\n\t\t},\r\n\t\trequest: 'add',\r\n\t\tasync add(target, room, user, connection) {\r\n\t\t\tFriends.checkCanUse(this);\r\n\t\t\ttarget = toID(target);\r\n\t\t\tif (target.length > 18) {\r\n\t\t\t\treturn this.errorReply(this.tr`That name is too long - choose a valid name.`);\r\n\t\t\t}\r\n\t\t\tif (!target) return this.parse('/help friends');\r\n\t\t\tawait Friends.request(user, target as ID);\r\n\t\t\tthis.refreshPage('friends-sent');\r\n\t\t\treturn this.sendReply(`You sent a friend request to '${target}'.`);\r\n\t\t},\r\n\t\tunfriend: 'remove',\r\n\t\tasync remove(target, room, user) {\r\n\t\t\tFriends.checkCanUse(this);\r\n\t\t\ttarget = toID(target);\r\n\t\t\tif (!target) return this.parse('/help friends');\r\n\r\n\t\t\tawait Friends.removeFriend(user.id, target as ID);\r\n\t\t\tthis.sendReply(`Removed friend '${target}'.`);\r\n\r\n\t\t\tawait Chat.Friends.updateUserCache(user);\r\n\t\t\tthis.refreshPage('friends-all');\r\n\t\t\tconst targetUser = Users.get(target);\r\n\t\t\tif (targetUser) await Chat.Friends.updateUserCache(targetUser);\r\n\t\t},\r\n\t\tview(target) {\r\n\t\t\treturn this.parse(`/join view-friends-${target}`);\r\n\t\t},\r\n\t\tlist() {\r\n\t\t\treturn this.parse(`/join view-friends-all`);\r\n\t\t},\r\n\t\tasync accept(target, room, user, connection) {\r\n\t\t\tFriends.checkCanUse(this);\r\n\t\t\ttarget = toID(target);\r\n\t\t\tif (user.settings.blockFriendRequests) {\r\n\t\t\t\treturn this.errorReply(this.tr`You are currently blocking friend requests, and so cannot accept your own.`);\r\n\t\t\t}\r\n\t\t\tif (!target) return this.parse('/help friends');\r\n\t\t\tawait Friends.approveRequest(user.id, target as ID);\r\n\t\t\tconst targetUser = Users.get(target);\r\n\t\t\tsendPM(`You accepted a friend request from \"${target}\".`, user.id);\r\n\t\t\tthis.refreshPage('friends-received');\r\n\t\t\tif (targetUser) {\r\n\t\t\t\tsendPM(`/text ${user.name} accepted your friend request!`, targetUser.id);\r\n\t\t\t\tsendPM(`/uhtmlchange sent-${targetUser.id},`, targetUser.id);\r\n\t\t\t\tsendPM(`/uhtmlchange undo-${targetUser.id},`, targetUser.id);\r\n\t\t\t}\r\n\t\t\tawait Chat.Friends.updateUserCache(user);\r\n\t\t\tif (targetUser) await Chat.Friends.updateUserCache(targetUser);\r\n\t\t},\r\n\t\tdeny: 'reject',\r\n\t\tasync reject(target, room, user, connection) {\r\n\t\t\tFriends.checkCanUse(this);\r\n\t\t\ttarget = toID(target);\r\n\t\t\tif (!target) return this.parse('/help friends');\r\n\t\t\tconst res = await Friends.removeRequest(user.id, target as ID);\r\n\t\t\tif (!res.changes) {\r\n\t\t\t\treturn this.errorReply(`You do not have a friend request pending from '${target}'.`);\r\n\t\t\t}\r\n\t\t\tthis.refreshPage('friends-received');\r\n\t\t\treturn sendPM(`You denied a friend request from '${target}'.`, user.id);\r\n\t\t},\r\n\t\ttoggle(target, room, user, connection) {\r\n\t\t\tFriends.checkCanUse(this);\r\n\t\t\tconst setting = user.settings.blockFriendRequests;\r\n\t\t\ttarget = target.trim();\r\n\t\t\tif (this.meansYes(target)) {\r\n\t\t\t\tif (!setting) return this.errorReply(this.tr`You already are allowing friend requests.`);\r\n\t\t\t\tuser.settings.blockFriendRequests = false;\r\n\t\t\t\tthis.sendReply(this.tr`You are now allowing friend requests.`);\r\n\t\t\t} else if (this.meansNo(target)) {\r\n\t\t\t\tif (setting) return this.errorReply(this.tr`You already are blocking incoming friend requests.`);\r\n\t\t\t\tuser.settings.blockFriendRequests = true;\r\n\t\t\t\tthis.sendReply(this.tr`You are now blocking incoming friend requests.`);\r\n\t\t\t} else {\r\n\t\t\t\tif (target) this.errorReply(this.tr`Unrecognized setting.`);\r\n\t\t\t\tthis.sendReply(\r\n\t\t\t\t\tthis.tr(setting ? `You are currently blocking friend requests.` : `You are not blocking friend requests.`)\r\n\t\t\t\t);\r\n\t\t\t}\r\n\t\t\tthis.refreshPage('friends-settings');\r\n\t\t\tuser.update();\r\n\t\t},\r\n\t\tasync undorequest(target, room, user, connection) {\r\n\t\t\tFriends.checkCanUse(this);\r\n\t\t\ttarget = toID(target);\r\n\t\t\tawait Friends.removeRequest(target as ID, user.id);\r\n\t\t\tthis.refreshPage('friends-sent');\r\n\t\t\treturn sendPM(`You removed your friend request to '${target}'.`, user.id);\r\n\t\t},\r\n\t\thidenotifs: 'viewnotifications',\r\n\t\thidenotifications: 'viewnotifications',\r\n\t\tviewnotifs: 'viewnotifications',\r\n\t\tviewnotifications(target, room, user, connection, cmd) {\r\n\t\t\t// Friends.checkCanUse(this);\r\n\t\t\tconst setting = user.settings.allowFriendNotifications;\r\n\t\t\ttarget = target.trim();\r\n\t\t\tif (!cmd.includes('hide') || target && this.meansYes(target)) {\r\n\t\t\t\tif (setting) return this.errorReply(this.tr(`You are already allowing friend notifications.`));\r\n\t\t\t\tuser.settings.allowFriendNotifications = true;\r\n\t\t\t\tthis.sendReply(this.tr(`You will now receive friend notifications.`));\r\n\t\t\t} else if (cmd.includes('hide') || target && this.meansNo(target)) {\r\n\t\t\t\tif (!setting) return this.errorReply(this.tr`You are already not receiving friend notifications.`);\r\n\t\t\t\tuser.settings.allowFriendNotifications = false;\r\n\t\t\t\tthis.sendReply(this.tr`You will not receive friend notifications.`);\r\n\t\t\t} else {\r\n\t\t\t\tif (target) this.errorReply(this.tr`Unrecognized setting.`);\r\n\t\t\t\tthis.sendReply(\r\n\t\t\t\t\tthis.tr(setting ? `You are currently allowing friend notifications.` : `Your friend notifications are disabled.`)\r\n\t\t\t\t);\r\n\t\t\t}\r\n\t\t\tthis.refreshPage('friends-settings');\r\n\t\t\tuser.update();\r\n\t\t},\r\n\t\thidelogins: 'togglelogins',\r\n\t\tshowlogins: 'togglelogins',\r\n\t\tasync togglelogins(target, room, user, connection, cmd) {\r\n\t\t\tFriends.checkCanUse(this);\r\n\t\t\tconst setting = user.settings.hideLogins;\r\n\t\t\tif (cmd.includes('hide')) {\r\n\t\t\t\tif (setting) return this.errorReply(this.tr`You are already hiding your logins from friends.`);\r\n\t\t\t\tuser.settings.hideLogins = true;\r\n\t\t\t\tawait Chat.Friends.hideLoginData(user.id);\r\n\t\t\t\tthis.sendReply(`You are now hiding your login times from your friends.`);\r\n\t\t\t} else if (cmd.includes('show')) {\r\n\t\t\t\tif (!setting) return this.errorReply(this.tr`You are already allowing friends to see your login times.`);\r\n\t\t\t\tuser.settings.hideLogins = false;\r\n\t\t\t\tawait Chat.Friends.allowLoginData(user.id);\r\n\t\t\t\tthis.sendReply(`You are now allowing your friends to see your login times.`);\r\n\t\t\t} else {\r\n\t\t\t\treturn this.errorReply(`Invalid setting.`);\r\n\t\t\t}\r\n\t\t\tthis.refreshPage('friends-settings');\r\n\t\t\tuser.update();\r\n\t\t},\r\n\t\tasync listdisplay(target, room, user, connection) {\r\n\t\t\tFriends.checkCanUse(this);\r\n\t\t\ttarget = toID(target);\r\n\t\t\tconst {public_list: setting} = await Chat.Friends.getSettings(user.id);\r\n\t\t\tif (this.meansYes(target)) {\r\n\t\t\t\tif (setting) {\r\n\t\t\t\t\treturn this.errorReply(this.tr`You are already allowing other people to view your friends list.`);\r\n\t\t\t\t}\r\n\t\t\t\tawait Chat.Friends.setHideList(user.id, true);\r\n\t\t\t\tthis.refreshPage('friends-settings');\r\n\t\t\t\treturn this.sendReply(this.tr`You are now allowing other people to view your friends list.`);\r\n\t\t\t} else if (this.meansNo(target)) {\r\n\t\t\t\tif (!setting) {\r\n\t\t\t\t\treturn this.errorReply(this.tr`You are already hiding your friends list.`);\r\n\t\t\t\t}\r\n\t\t\t\tawait Chat.Friends.setHideList(user.id, false);\r\n\t\t\t\tthis.refreshPage('friends-settings');\r\n\t\t\t\treturn this.sendReply(this.tr`You are now hiding your friends list.`);\r\n\t\t\t}\r\n\t\t\tthis.sendReply(`You are currently ${setting ? 'displaying' : 'hiding'} your friends list.`);\r\n\t\t},\r\n\t\tinvalidatecache(target, room, user) {\r\n\t\t\tthis.canUseConsole();\r\n\t\t\tfor (const curUser of Users.users.values()) {\r\n\t\t\t\tvoid Chat.Friends.updateUserCache(curUser);\r\n\t\t\t}\r\n\t\t\tRooms.global.notifyRooms(\r\n\t\t\t\t['staff', 'development'],\r\n\t\t\t\t`|c|${user.getIdentity()}|/log ${user.name} used /friends invalidatecache`,\r\n\t\t\t);\r\n\t\t\tthis.sendReply(`You invalidated each entry in the friends database cache.`);\r\n\t\t},\r\n\t\tsharebattles(target, room, user) {\r\n\t\t\tFriends.checkCanUse(this);\r\n\t\t\ttarget = toID(target);\r\n\t\t\tif (this.meansYes(target)) {\r\n\t\t\t\tif (user.settings.displayBattlesToFriends) {\r\n\t\t\t\t\treturn this.errorReply(this.tr`You are already sharing your battles with friends.`);\r\n\t\t\t\t}\r\n\t\t\t\tuser.settings.displayBattlesToFriends = true;\r\n\t\t\t\tthis.sendReply(`You are now allowing your friends to see your ongoing battles.`);\r\n\t\t\t} else if (this.meansNo(target)) {\r\n\t\t\t\tif (!user.settings.displayBattlesToFriends) {\r\n\t\t\t\t\treturn this.errorReply(this.tr`You are already not sharing your battles with friends.`);\r\n\t\t\t\t}\r\n\t\t\t\tuser.settings.displayBattlesToFriends = false;\r\n\t\t\t\tthis.sendReply(`You are now hiding your ongoing battles from your friends.`);\r\n\t\t\t} else {\r\n\t\t\t\tif (!target) return this.parse('/help friends sharebattles');\r\n\t\t\t\treturn this.errorReply(`Invalid setting '${target}'. Provide 'on' or 'off'.`);\r\n\t\t\t}\r\n\t\t\tuser.update();\r\n\t\t\tthis.refreshPage('friends-settings');\r\n\t\t},\r\n\t\tsharebattleshelp: [\r\n\t\t\t`/friends sharebattles [on|off] - Allow or disallow your friends from seeing your ongoing battles.`,\r\n\t\t],\r\n\t},\r\n\tfriendshelp() {\r\n\t\tthis.runBroadcast();\r\n\t\tif (this.broadcasting) {\r\n\t\t\treturn this.sendReplyBox([\r\n\t\t\t\t`/friend list - View current friends.`,\r\n\t\t\t\t`/friend add [name] OR /friend [name] - Send a friend request to [name], if you don't have them added.`,\r\n\t\t\t\t`/friend remove [username] OR /unfriend [username] - Unfriend the user.`,\r\n\t\t\t\t`
More commands...`,\r\n\t\t\t\t`/friend accept [username] - Accepts the friend request from [username], if it exists.`,\r\n\t\t\t\t`/friend reject [username] - Rejects the friend request from [username], if it exists.`,\r\n\t\t\t\t`/friend toggle [off/on] - Enable or disable receiving of friend requests.`,\r\n\t\t\t\t`/friend hidenotifications OR hidenotifs - Opts out of receiving friend notifications.`,\r\n\t\t\t\t`/friend viewnotifications OR viewnotifs - Opts into view friend notifications.`,\r\n\t\t\t\t`/friend listdisplay [on/off] - Opts [in/out] of letting others view your friends list.`,\r\n\t\t\t\t`/friend viewlist [user] - View the given [user]'s friend list, if they're allowing others to see.`,\r\n\t\t\t\t`/friends sharebattles [on|off] - Allow or disallow your friends from seeing your ongoing battles.
`,\r\n\t\t\t].join('
'));\r\n\t\t}\r\n\t\treturn this.parse('/join view-friends-help');\r\n\t},\r\n};\r\n\r\nexport const pages: Chat.PageTable = {\r\n\tasync friends(args, user) {\r\n\t\tif (!user.named) return Rooms.RETRY_AFTER_LOGIN;\r\n\t\tFriends.checkCanUse(this);\r\n\t\tconst type = args.shift();\r\n\t\tlet buf = '
';\r\n\t\tswitch (toID(type)) {\r\n\t\tcase 'outgoing': case 'sent':\r\n\t\t\tthis.title = `[Friends] Sent`;\r\n\t\t\tbuf += headerButtons('sent', user);\r\n\t\t\tif (user.settings.blockFriendRequests) {\r\n\t\t\t\tbuf += `

${this.tr(`You are currently blocking friend requests`)}.

`;\r\n\t\t\t}\r\n\t\t\tconst {sent} = await Chat.Friends.getRequests(user);\r\n\t\t\tif (sent.size < 1) {\r\n\t\t\t\tbuf += `You have no outgoing friend requests pending.
`;\r\n\t\t\t\tbuf += `
To add a friend, use /friend add [username].`;\r\n\t\t\t\tbuf += `
`;\r\n\t\t\t\treturn toLink(buf);\r\n\t\t\t}\r\n\t\t\tbuf += `

You have ${Chat.count(sent.size, 'friend requests')} pending${sent.size === MAX_REQUESTS ? ` (maximum reached)` : ''}.

`;\r\n\t\t\tfor (const request of sent) {\r\n\t\t\t\tbuf += `
`;\r\n\t\t\t\tbuf += `${request}`;\r\n\t\t\t\tbuf += ` `;\r\n\t\t\t\tbuf += `
`;\r\n\t\t\t}\r\n\t\t\tbreak;\r\n\t\tcase 'received': case 'incoming':\r\n\t\t\tthis.title = `[Friends] Received`;\r\n\t\t\tbuf += headerButtons('received', user);\r\n\t\t\tconst {received} = await Chat.Friends.getRequests(user);\r\n\t\t\tif (received.size < 1) {\r\n\t\t\t\tbuf += `You have no pending friend requests.`;\r\n\t\t\t\tbuf += ``;\r\n\t\t\t\treturn toLink(buf);\r\n\t\t\t}\r\n\t\t\tbuf += `

You have ${received.size} pending friend requests.

`;\r\n\t\t\tfor (const request of received) {\r\n\t\t\t\tbuf += `
`;\r\n\t\t\t\tbuf += `${request}`;\r\n\t\t\t\tbuf += ` |`;\r\n\t\t\t\tbuf += ` `;\r\n\t\t\t\tbuf += `
`;\r\n\t\t\t}\r\n\t\t\tbreak;\r\n\t\tcase 'viewuser':\r\n\t\t\tconst target = toID(args.shift());\r\n\t\t\tif (!target) return this.errorReply(`Specify a user.`);\r\n\t\t\tif (target === user.id) {\r\n\t\t\t\treturn this.errorReply(`Use /friends list to view your own list.`);\r\n\t\t\t}\r\n\t\t\tconst {public_list: isAllowing} = await Chat.Friends.getSettings(target);\r\n\t\t\tif (!isAllowing) return this.errorReply(`${target}'s friends list is not public or they do not have one.`);\r\n\t\t\tthis.title = `[Friends List] ${target}`;\r\n\t\t\tbuf += await Friends.visualizePublicList(target);\r\n\t\t\tbreak;\r\n\t\tcase 'help':\r\n\t\t\tthis.title = `[Friends] Help`;\r\n\t\t\tbuf += headerButtons('help', user);\r\n\t\t\tbuf += `

Help

`;\r\n\t\t\tbuf += `/friend OR /friends OR /friendslist:
`;\r\n\t\t\tbreak;\r\n\t\tcase 'settings':\r\n\t\t\tthis.title = `[Friends] Settings`;\r\n\t\t\tbuf += headerButtons('settings', user);\r\n\t\t\tbuf += `

Friends Settings:

`;\r\n\t\t\tconst settings = user.settings;\r\n\t\t\tconst {public_list, send_login_data} = await Chat.Friends.getSettings(user.id);\r\n\t\t\tbuf += `Notify me when my friends come online:
`;\r\n\t\t\tbuf += ` `;\r\n\t\t\tbuf += `

`;\r\n\r\n\t\t\tbuf += `Receive friend requests:
`;\r\n\t\t\tbuf += ` `;\r\n\t\t\tbuf += `

`;\r\n\r\n\t\t\tbuf += `Allow others to see your list:
`;\r\n\t\t\tbuf += ` `;\r\n\t\t\tbuf += `

`;\r\n\r\n\t\t\tbuf += `Allow others to see my login times
`;\r\n\t\t\tbuf += ` `;\r\n\t\t\tbuf += `

`;\r\n\r\n\t\t\tbuf += `Allow friends to see my hidden battles on the spectator list:
`;\r\n\t\t\tbuf += ` `;\r\n\t\t\tbuf += `

`;\r\n\r\n\t\t\tbuf += `Block PMs except from friends (and staff):
`;\r\n\t\t\tbuf += ` `;\r\n\t\t\tbuf += `

`;\r\n\r\n\t\t\tbuf += `Block challenges except from friends (and staff):
`;\r\n\t\t\tbuf += ` `;\r\n\t\t\tbuf += `

`;\r\n\t\t\tbreak;\r\n\t\tcase 'spectate':\r\n\t\t\tthis.title = `[Friends] Spectating`;\r\n\t\t\tbuf += headerButtons('spectate', user);\r\n\t\t\tbuf += `

Spectate your friends:

`;\r\n\r\n\t\t\tconst toggleMessage = user.settings.displayBattlesToFriends ?\r\n\t\t\t\t' disallow your friends from seeing your hidden battles' :\r\n\t\t\t\t' allow your friends to see your hidden battles';\r\n\t\t\tbuf += `Use the settings page to ${toggleMessage} on this page.
`;\r\n\t\t\tbuf += `
`;\r\n\t\t\tif (!user.friends?.size) {\r\n\t\t\t\tbuf += `

You have no friends to spectate.

`;\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t\tconst friends = [];\r\n\t\t\tfor (const friendID of user.friends) {\r\n\t\t\t\tconst friend = Users.getExact(friendID);\r\n\t\t\t\tif (!friend) continue;\r\n\t\t\t\tfriends.push(friend);\r\n\t\t\t}\r\n\t\t\tif (!friends.length) {\r\n\t\t\t\tbuf += `None of your friends are currently around to spectate.`;\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\r\n\t\t\tconst battles: [User, string][] = [];\r\n\t\t\tfor (const friend of friends) {\r\n\t\t\t\tconst curBattles: [User, string][] = [...friend.inRooms]\r\n\t\t\t\t\t.filter(id => {\r\n\t\t\t\t\t\tconst battle = Rooms.get(id)?.battle;\r\n\t\t\t\t\t\treturn (\r\n\t\t\t\t\t\t\tbattle && battle.playerTable[friend.id] &&\r\n\t\t\t\t\t\t\t(!battle.roomid.endsWith('pw') || friend.settings.displayBattlesToFriends)\r\n\t\t\t\t\t\t);\r\n\t\t\t\t\t})\r\n\t\t\t\t\t.map(id => [friend, id]);\r\n\t\t\t\tif (!curBattles.length) continue;\r\n\t\t\t\tbattles.push(...curBattles);\r\n\t\t\t}\r\n\t\t\tUtils.sortBy(battles, ([, id]) => -Number(id.split('-')[2]));\r\n\r\n\t\t\tif (!battles.length) {\r\n\t\t\t\tbuf += `None of your friends are currently in a battle.`;\r\n\t\t\t} else {\r\n\t\t\t\tbuf += battles.map(([friend, battle]) => {\r\n\t\t\t\t\t// we've already ensured the battle exists in the filter above\r\n\t\t\t\t\t// (and .battle only exists if it's a GameRoom, so this cast is safe)\r\n\t\t\t\t\tconst room = Rooms.get(battle) as GameRoom & {battle: Rooms.RoomBattle};\r\n\t\t\t\t\tconst format = Dex.formats.get(room.battle.format).name;\r\n\t\t\t\t\tconst rated = room.battle.rated ? `(Rated: ${room.battle.rated})` : '';\r\n\t\t\t\t\tconst title = room.title.includes(friend.name) ?\r\n\t\t\t\t\t\troom.title.replace(friend.name, `${friend.name}`) :\r\n\t\t\t\t\t\t(room.title + ` (with ${friend.name})`);\r\n\t\t\t\t\treturn `[${format}]${rated}
${title}
`;\r\n\t\t\t\t}).join('
');\r\n\t\t\t}\r\n\t\t\tbreak;\r\n\t\tdefault:\r\n\t\t\tthis.title = `[Friends] All Friends`;\r\n\t\t\tbuf += headerButtons('all', user);\r\n\t\t\tbuf += await Friends.visualizeList(user.id);\r\n\t\t}\r\n\t\tbuf += ``;\r\n\t\treturn toLink(buf);\r\n\t},\r\n};\r\n\r\nexport const handlers: Chat.Handlers = {\r\n\tonBattleStart(user) {\r\n\t\treturn Friends.updateSpectatorLists(user);\r\n\t},\r\n\tonBattleLeave(user, room) {\r\n\t\treturn Friends.updateSpectatorLists(user);\r\n\t},\r\n\tonBattleEnd(battle, winner, players) {\r\n\t\tfor (const id of players) {\r\n\t\t\tconst user = Users.get(id);\r\n\t\t\tif (!user) continue;\r\n\t\t\tFriends.updateSpectatorLists(user);\r\n\t\t}\r\n\t},\r\n\tonDisconnect(user) {\r\n\t\tvoid Chat.Friends.writeLogin(user.id);\r\n\t},\r\n};\r\n\r\nexport const loginfilter: Chat.LoginFilter = user => {\r\n\tif (!Config.usesqlitefriends || !Users.globalAuth.atLeast(user, Config.usesqlitefriends)) {\r\n\t\treturn;\r\n\t}\r\n\r\n\t// notify users of pending requests\r\n\tvoid Friends.notifyPending(user);\r\n\r\n\t// (quietly) notify their friends (that have opted in) that they are online\r\n\tvoid Friends.notifyConnection(user);\r\n\t// write login time\r\n\tvoid Chat.Friends.writeLogin(user.id);\r\n\r\n\tvoid Chat.Friends.updateUserCache(user);\r\n};\r\n"], "mappings": "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", "names": [] }