Tweaks to !server numbers. Try to prevent unnecessary API calls.

!server can now also use numbers from the invite of servers with
a vanity URL.

API calls for the invite and the REST guild endpoint are now memoized.

Since Guild.fetchAllMembers() now returns a promise, tweaked
refreshMembersIfNeeded() to not make unnecessary API calls if called
multiple times in rapid succession.
This commit is contained in:
Dragory 2020-01-21 00:24:04 +02:00
parent ba647c69ce
commit 6a5e71d7c1
No known key found for this signature in database
GPG key ID: 5F387BA66DF8AAC1
2 changed files with 61 additions and 9 deletions

View file

@ -44,6 +44,7 @@ import {
trimLines,
UnknownUser,
downloadFile,
memoize,
} from "../utils";
import { GuildLogs } from "../data/GuildLogs";
import { LogType } from "../data/LogType";
@ -103,8 +104,8 @@ const SEARCH_ID_RESULTS_PER_PAGE = 50;
const MAX_CLEAN_COUNT = 150;
const MAX_CLEAN_TIME = 1 * DAYS;
const CLEAN_COMMAND_DELETE_DELAY = 5000;
const MEMBER_REFRESH_FREQUENCY = 10 * 60 * 1000; // How often to do a full member refresh when using !search or !roles --counts
const CLEAN_COMMAND_DELETE_DELAY = 5 * SECONDS;
const MEMBER_REFRESH_FREQUENCY = 10 * MINUTES; // How often to do a full member refresh when using commands that need it
const SEARCH_EXPORT_LIMIT = 1_000_000;
const activeReloads: Map<string, TextChannel> = new Map();
@ -137,6 +138,7 @@ export class UtilityPlugin extends ZeppelinPlugin<TConfigSchema> {
protected archives: GuildArchives;
protected lastFullMemberRefresh = 0;
protected fullMemberRefreshPromise;
protected lastReload;
public static getStaticDefaultOptions(): IPluginOptions<TConfigSchema> {
@ -206,9 +208,14 @@ export class UtilityPlugin extends ZeppelinPlugin<TConfigSchema> {
}
protected async refreshMembersIfNeeded() {
if (Date.now() < this.lastFullMemberRefresh + MEMBER_REFRESH_FREQUENCY) return;
await this.guild.fetchAllMembers();
if (Date.now() < this.lastFullMemberRefresh + MEMBER_REFRESH_FREQUENCY) {
return this.fullMemberRefreshPromise;
}
this.lastFullMemberRefresh = Date.now();
this.fullMemberRefreshPromise = this.guild.fetchAllMembers();
return this.fullMemberRefreshPromise;
}
@d.command("roles", "[search:string$]", {
@ -1022,7 +1029,7 @@ export class UtilityPlugin extends ZeppelinPlugin<TConfigSchema> {
})
@d.permission("can_server")
async serverCmd(msg: Message) {
await this.guild.fetchAllMembers();
await this.refreshMembersIfNeeded();
const embed: EmbedOptions = {
fields: [],
@ -1051,16 +1058,34 @@ export class UtilityPlugin extends ZeppelinPlugin<TConfigSchema> {
`) + embedPadding,
});
const onlineMemberCount = this.guild.members.filter(m => m.status !== "offline").length;
const restGuild = await memoize(
() => this.bot.getRESTGuild(this.guildId),
`getRESTGuild_${this.guildId}`,
10 * MINUTES,
);
// For servers with a vanity URL, we can use the numbers from the invite for online count
// (which is nowadays usually more accurate for large servers)
const invite = this.guild.vanityURL
? await memoize(
() => this.bot.getInvite(this.guild.vanityURL, true),
`getInvite_${this.guild.vanityURL}`,
10 * MINUTES,
)
: null;
const totalMembers = invite ? invite.memberCount : this.guild.memberCount;
const onlineMemberCount = invite
? invite.presenceCount
: this.guild.members.filter(m => m.status !== "offline").length;
const offlineMemberCount = this.guild.memberCount - onlineMemberCount;
const onlineStatusMemberCount = this.guild.members.filter(m => m.status === "online").length;
const dndStatusMemberCount = this.guild.members.filter(m => m.status === "dnd").length;
const idleStatusMemberCount = this.guild.members.filter(m => m.status === "idle").length;
const restGuild = await this.bot.getRESTGuild(this.guildId);
let memberCountTotalLines = `Total: **${formatNumber(this.guild.memberCount)}**`;
let memberCountTotalLines = `Total: **${formatNumber(totalMembers)}**`;
if (restGuild.maxMembers) {
memberCountTotalLines += `\nMax: **${formatNumber(restGuild.maxMembers)}**`;
}

View file

@ -1049,3 +1049,30 @@ const formatter = new Intl.NumberFormat("en-US");
export function formatNumber(numberToFormat: number): string {
return formatter.format(numberToFormat);
}
interface IMemoizedItem {
createdAt: number;
value: any;
}
const memoizeCache: Map<any, IMemoizedItem> = new Map();
export function memoize<T>(fn: (...args: any[]) => T, key?, time?): T {
const realKey = key ?? fn;
if (memoizeCache.has(realKey)) {
const memoizedItem = memoizeCache.get(realKey);
if (!time || memoizedItem.createdAt > Date.now() - time) {
return memoizedItem.value;
}
memoizeCache.delete(realKey);
}
const value = fn();
memoizeCache.set(realKey, {
createdAt: Date.now(),
value,
});
return value;
}