Spaces:
Paused
Paused
| import { Utils, FS } from '../../lib'; | |
| export const nameList = new Set<string>(JSON.parse( | |
| FS('config/chat-plugins/usersearch.json').readIfExistsSync() || "[]" | |
| )); | |
| const ONLINE_SYMBOL = ` \u25C9 `; | |
| const OFFLINE_SYMBOL = ` \u25CC `; | |
| class PunishmentHTML extends Chat.JSX.Component<{ userid: ID, target: string }> { | |
| render() { | |
| const { userid, target } = { ...this.props }; | |
| const buf = []; | |
| for (const cmdName of ['Forcerename', 'Namelock', 'Weeknamelock']) { | |
| // We have to use dangerouslySetInnerHTML here because otherwise the `value` | |
| // property of the button tag is auto escaped, making into &#10; | |
| buf.push(<span | |
| dangerouslySetInnerHTML={{ | |
| __html: `<button class="button" name="send" value="/msgroom staff,/${toID(cmdName)} ${userid}` + | |
| ` /uspage ${target}">${cmdName}</button>`, | |
| }} | |
| />); | |
| } | |
| return buf; | |
| } | |
| } | |
| class SearchUsernames extends Chat.JSX.Component<{ target: string, page?: boolean }> { | |
| render() { | |
| const { target, page } = { ...this.props }; | |
| const results: { offline: string[], online: string[] } = { | |
| offline: [], | |
| online: [], | |
| }; | |
| for (const curUser of Users.users.values()) { | |
| if (!curUser.id.includes(target) || curUser.id.startsWith('guest')) continue; | |
| if (Punishments.isGlobalBanned(curUser)) continue; | |
| if (curUser.connected) { | |
| results.online.push(`${!page ? ONLINE_SYMBOL : ''} ${curUser.name}`); | |
| } else { | |
| results.offline.push(`${!page ? OFFLINE_SYMBOL : ''} ${curUser.name}`); | |
| } | |
| } | |
| for (const k in results) { | |
| Utils.sortBy(results[k as keyof typeof results], result => toID(result)); | |
| } | |
| if (!page) { | |
| return <> | |
| Users with a name matching '{target}':<br /> | |
| {!results.offline.length && !results.online.length ? ( | |
| <>No users found.</> | |
| ) : ( | |
| <> | |
| {results.online.join('; ')} | |
| {!!results.offline.length && | |
| <>{!!results.online.length && <><br /><br /></>}{results.offline.join('; ')}</>} | |
| </> | |
| )} | |
| </>; | |
| } | |
| return <div class="pad"> | |
| <h2>Usernames containing "{target}"</h2> | |
| {!results.online.length && !results.offline.length ? ( | |
| <p>No results found.</p> | |
| ) : ( | |
| <>{!!results.online.length && <div class="ladder pad"> | |
| <h3>Online users</h3> | |
| <table> | |
| <tr> | |
| <th>Username</th> | |
| <th>Punish</th> | |
| </tr> | |
| {(() => { | |
| const online = []; | |
| for (const username of results.online) { | |
| online.push(<tr> | |
| <td><username>{username}</username></td> | |
| <td><PunishmentHTML userid={toID(username)} target={target} /></td> | |
| </tr>); | |
| } | |
| return online; | |
| })()} | |
| </table> | |
| </div>} | |
| {!!(results.online.length && results.offline.length) && <hr />} | |
| {!!results.offline.length && <div class="ladder pad"> | |
| <h3>Offline users</h3> | |
| <table> | |
| <tr> | |
| <th>Username</th> | |
| <th>Punish</th> | |
| </tr> | |
| {(() => { | |
| const offline = []; | |
| for (const username of results.offline) { | |
| offline.push(<tr> | |
| <td><username>{username}</username></td> | |
| <td><PunishmentHTML userid={toID(username)} target={target} /></td> | |
| </tr>); | |
| } | |
| return offline; | |
| })()} | |
| </table> | |
| </div>}</> | |
| )} | |
| </div>; | |
| } | |
| } | |
| function saveNames() { | |
| FS('config/chat-plugins/usersearch.json').writeUpdate(() => JSON.stringify([...nameList])); | |
| } | |
| export const commands: Chat.ChatCommands = { | |
| us: 'usersearch', | |
| uspage: 'usersearch', | |
| usersearchpage: 'usersearch', | |
| usersearch(target, room, user, connection, cmd) { | |
| this.checkCan('lock'); | |
| target = toID(target); | |
| if (!target) { // just join directly if it's the page cmd, they're likely looking for the full list | |
| if (cmd.includes('page')) return this.parse(`/j view-usersearch`); | |
| return this.parse(`/help usersearch`); | |
| } | |
| if (target.length < 3) { | |
| throw new Chat.ErrorMessage(`That's too short of a term to search for.`); | |
| } | |
| const showPage = cmd.includes('page'); | |
| if (showPage) { | |
| this.parse(`/j view-usersearch-${target}`); | |
| return; | |
| } | |
| return this.sendReplyBox(<SearchUsernames target={target} />); | |
| }, | |
| usersearchhelp: [ | |
| `/usersearch [pattern]: Looks for all names matching the [pattern]. Requires: % @ ~`, | |
| `Adding "page" to the end of the command, i.e. /usersearchpage OR /uspage will bring up a page.`, | |
| `See also /usnames for a staff-curated list of the most commonly searched terms.`, | |
| ], | |
| usnames: 'usersearchnames', | |
| usersearchnames: { | |
| '': 'list', | |
| list() { | |
| this.parse(`/join view-usersearch`); | |
| }, | |
| add(target, room, user) { | |
| this.checkCan('lock'); | |
| const targets = target.split(',').map(toID).filter(Boolean); | |
| if (!targets.length) { | |
| return this.errorReply(`Specify at least one term.`); | |
| } | |
| for (const [i, arg] of targets.entries()) { | |
| if (nameList.has(arg)) { | |
| targets.splice(i, 1); | |
| this.errorReply(`Term ${arg} is already on the usersearch term list.`); | |
| continue; | |
| } | |
| if (arg.length < 3) { | |
| targets.splice(i, 1); | |
| this.errorReply(`Term ${arg} is too short for the usersearch term list. Must be more than 3 characters.`); | |
| continue; | |
| } | |
| nameList.add(arg); | |
| } | |
| if (!targets.length) { | |
| // fuck you too, "mia added 0 term to the usersearch name list" | |
| return this.errorReply(`No terms could be added.`); | |
| } | |
| const count = Chat.count(targets, 'terms'); | |
| Rooms.get('staff')?.addByUser( | |
| user, `${user.name} added the ${count} "${targets.join(', ')}" to the usersearch name list.` | |
| ); | |
| this.globalModlog(`USERSEARCH ADD`, null, targets.join(', ')); | |
| if (!room || room.roomid !== 'staff') { | |
| this.sendReply(`You added the ${count} "${targets.join(', ')}" to the usersearch name list.`); | |
| } | |
| saveNames(); | |
| }, | |
| remove(target, room, user) { | |
| this.checkCan('lock'); | |
| const targets = target.split(',').map(toID).filter(Boolean); | |
| if (!targets.length) { | |
| return this.errorReply(`Specify at least one term.`); | |
| } | |
| for (const [i, arg] of targets.entries()) { | |
| if (!nameList.has(arg)) { | |
| targets.splice(i, 1); | |
| this.errorReply(`${arg} is not in the usersearch name list, and has been skipped.`); | |
| continue; | |
| } | |
| nameList.delete(arg); | |
| } | |
| if (!targets.length) { | |
| return this.errorReply(`No terms could be removed.`); | |
| } | |
| const count = Chat.count(targets, 'terms'); | |
| Rooms.get('staff')?.addByUser( | |
| user, `${user.name} removed the ${count} "${targets.join(', ')}" from the usersearch name list.` | |
| ); | |
| this.globalModlog(`USERSEARCH REMOVE`, null, targets.join(', ')); | |
| if (!room || room.roomid !== 'staff') { | |
| this.sendReply(`You removed the ${count} "${targets.join(', ')}"" from the usersearch name list.`); | |
| } | |
| saveNames(); | |
| }, | |
| }, | |
| usnameshelp: [ | |
| `/usnames add [...terms]: Adds the given [terms] to the usersearch name list. Requires: % @ ~`, | |
| `/usnames remove [...terms]: Removes the given [terms] from the usersearch name list. Requires: % @ ~`, | |
| `/usnames OR /usnames list: Shows the usersearch name list.`, | |
| ], | |
| }; | |
| export const pages: Chat.PageTable = { | |
| usersearch(query, user) { | |
| this.checkCan('lock'); | |
| const target = toID(query.shift()); | |
| if (!target) { | |
| this.title = `[Usersearch Terms]`; | |
| const sorted: { [k: string]: number } = {}; | |
| for (const curUser of Users.users.values()) { | |
| for (const term of nameList) { | |
| if (curUser.id.includes(term)) { | |
| if (!(term in sorted)) sorted[term] = 0; | |
| sorted[term]++; | |
| } | |
| } | |
| } | |
| return <div class="pad"> | |
| <strong>Usersearch term list</strong> | |
| <button style={{ float: 'right' }} class="button" name="send" value="/uspage"> | |
| <i class="fa fa-refresh"></i> Refresh | |
| </button> | |
| <hr /> | |
| {!nameList.size ? ( | |
| <p>None found.</p> | |
| ) : ( | |
| <div class="ladder pad"> | |
| <table> | |
| <tr> | |
| <th>Term</th> | |
| <th>Current Matches</th> | |
| <th></th> | |
| </tr> | |
| {(() => { | |
| const buf = []; | |
| for (const k of Utils.sortBy(Object.keys(sorted), v => -sorted[v])) { | |
| buf.push(<tr> | |
| <td>{k}</td> | |
| <td>{sorted[k]}</td> | |
| <td><button class="button" name="send" value={`/uspage ${k}`}>Search</button></td> | |
| </tr>); | |
| } | |
| if (!buf.length) return <tr><td colSpan={3} style={{ textAlign: 'center' }}>No names found.</td></tr>; | |
| return buf; | |
| })()} | |
| </table> | |
| </div> | |
| )} | |
| </div>; | |
| } | |
| this.title = `[Usersearch] ${target}`; | |
| return <SearchUsernames target={target} page />; | |
| }, | |
| }; | |