mirror of
https://github.com/ZeppelinBot/Zeppelin.git
synced 2025-05-10 12:25:02 +00:00
Initial work on stats
This commit is contained in:
parent
26c460e67a
commit
56fb432c7c
6 changed files with 347 additions and 4 deletions
155
backend/src/plugins/Stats.ts
Normal file
155
backend/src/plugins/Stats.ts
Normal file
|
@ -0,0 +1,155 @@
|
|||
import { ZeppelinPlugin } from "./ZeppelinPlugin";
|
||||
import * as t from "io-ts";
|
||||
import { convertDelayStringToMS, DAYS, HOURS, tAlphanumeric, tDateTime, tDeepPartial, tDelayString } from "../utils";
|
||||
import { IPluginOptions } from "knub";
|
||||
import moment from "moment-timezone";
|
||||
import { GuildStats } from "../data/GuildStats";
|
||||
import { Message } from "eris";
|
||||
import escapeStringRegexp from "escape-string-regexp";
|
||||
import { SavedMessage } from "../data/entities/SavedMessage";
|
||||
import { GuildSavedMessages } from "../data/GuildSavedMessages";
|
||||
|
||||
const tBaseSource = t.type({
|
||||
name: tAlphanumeric,
|
||||
track: t.boolean,
|
||||
retention_period: tDelayString,
|
||||
});
|
||||
|
||||
const tMemberMessagesSource = t.intersection([
|
||||
tBaseSource,
|
||||
t.type({
|
||||
type: t.literal("member_messages"),
|
||||
}),
|
||||
]);
|
||||
type TMemberMessagesSource = t.TypeOf<typeof tMemberMessagesSource>;
|
||||
|
||||
const tChannelMessagesSource = t.intersection([
|
||||
tBaseSource,
|
||||
t.type({
|
||||
type: t.literal("channel_messages"),
|
||||
}),
|
||||
]);
|
||||
type TChannelMessagesSource = t.TypeOf<typeof tChannelMessagesSource>;
|
||||
|
||||
const tKeywordsSource = t.intersection([
|
||||
tBaseSource,
|
||||
t.type({
|
||||
type: t.literal("keywords"),
|
||||
keywords: t.array(t.string),
|
||||
}),
|
||||
]);
|
||||
type TKeywordsSource = t.TypeOf<typeof tKeywordsSource>;
|
||||
|
||||
const tSource = t.union([tMemberMessagesSource, tChannelMessagesSource, tKeywordsSource]);
|
||||
type TSource = t.TypeOf<typeof tSource>;
|
||||
|
||||
const tConfigSchema = t.type({
|
||||
sources: t.record(tAlphanumeric, tSource),
|
||||
});
|
||||
|
||||
type TConfigSchema = t.TypeOf<typeof tConfigSchema>;
|
||||
const tPartialConfigSchema = tDeepPartial(tConfigSchema);
|
||||
|
||||
const DEFAULT_RETENTION_PERIOD = "4w";
|
||||
|
||||
export class StatsPlugin extends ZeppelinPlugin<TConfigSchema> {
|
||||
public static pluginName = "stats";
|
||||
public static configSchema = tConfigSchema;
|
||||
public static showInDocs = false;
|
||||
|
||||
protected stats: GuildStats;
|
||||
protected savedMessages: GuildSavedMessages;
|
||||
|
||||
private onMessageCreateFn;
|
||||
private cleanStatsInterval;
|
||||
|
||||
public static getStaticDefaultOptions(): IPluginOptions<TConfigSchema> {
|
||||
return {
|
||||
config: {
|
||||
sources: {},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected static preprocessStaticConfig(config: t.TypeOf<typeof tPartialConfigSchema>) {
|
||||
// TODO: Limit min period, min period start date
|
||||
|
||||
if (config.sources) {
|
||||
for (const [key, source] of Object.entries(config.sources)) {
|
||||
source.name = key;
|
||||
|
||||
if (source.track == null) {
|
||||
source.track = true;
|
||||
}
|
||||
|
||||
if (source.retention_period == null) {
|
||||
source.retention_period = DEFAULT_RETENTION_PERIOD;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
protected onLoad() {
|
||||
this.stats = GuildStats.getGuildInstance(this.guildId);
|
||||
this.savedMessages = GuildSavedMessages.getGuildInstance(this.guildId);
|
||||
|
||||
this.onMessageCreateFn = this.savedMessages.events.on("create", msg => this.onMessageCreate(msg));
|
||||
|
||||
this.cleanOldStats();
|
||||
this.cleanStatsInterval = setInterval(() => this.cleanOldStats(), 1 * DAYS);
|
||||
}
|
||||
|
||||
protected onUnload() {
|
||||
this.savedMessages.events.off("create", this.onMessageCreateFn);
|
||||
clearInterval(this.cleanStatsInterval);
|
||||
}
|
||||
|
||||
protected async cleanOldStats() {
|
||||
const config = this.getConfig();
|
||||
for (const source of Object.values(config.sources)) {
|
||||
const cutoffMS = convertDelayStringToMS(source.retention_period);
|
||||
const cutoff = moment()
|
||||
.subtract(cutoffMS, "ms")
|
||||
.format("YYYY-MM-DD HH:mm:ss");
|
||||
await this.stats.deleteOldValues(source.name, cutoff);
|
||||
}
|
||||
}
|
||||
|
||||
protected saveMemberMessagesStats(source: TMemberMessagesSource, msg: SavedMessage) {
|
||||
this.stats.saveValue(source.name, msg.user_id, 1);
|
||||
}
|
||||
|
||||
protected saveChannelMessagesStats(source: TChannelMessagesSource, msg: SavedMessage) {
|
||||
this.stats.saveValue(source.name, msg.channel_id, 1);
|
||||
}
|
||||
|
||||
protected saveKeywordsStats(source: TKeywordsSource, msg: SavedMessage) {
|
||||
const content = msg.data.content;
|
||||
if (!content) return;
|
||||
|
||||
for (const keyword of source.keywords) {
|
||||
const regex = new RegExp(`\\b${escapeStringRegexp(keyword)}\\b`, "i");
|
||||
if (content.match(regex)) {
|
||||
this.stats.saveValue(source.name, "keyword", 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMessageCreate(msg: SavedMessage) {
|
||||
const config = this.getConfigForMemberIdAndChannelId(msg.user_id, msg.channel_id);
|
||||
for (const source of Object.values(config.sources)) {
|
||||
if (!source.track) continue;
|
||||
|
||||
if (source.type === "member_messages") {
|
||||
this.saveMemberMessagesStats(source, msg);
|
||||
} else if (source.type === "channel_messages") {
|
||||
this.saveChannelMessagesStats(source, msg);
|
||||
} else if (source.type === "keywords") {
|
||||
this.saveKeywordsStats(source, msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue