155 lines
4.6 KiB
TypeScript
155 lines
4.6 KiB
TypeScript
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);
|
|
}
|
|
}
|
|
}
|
|
}
|