Przejdź do treści

Inicjalizacja bota

Watchman.Discord

Jeśli uruchamiamy bota lokalnie, punktem wejściowym programu jest plik "Program" w projekcie "Watchman.Discord". Tutaj też uruchamiana jest czysta aplikacja samego bota, czyli tworzony i konfigurowany obiekt klasy "WatchmanBot".

Watchman.Web

Na produkcji bot jest uruchamiany w tle przez aplikację webową (projekt "Watchman.Web"). Za pomocą Hangfire jest obsługiwana część jego funkcji które mają być wykonywane okresowo.

W tym przypadku nasz obiekt "WatchmanBot" jest tworzony w pliku "Watchman.Web/ServiceProviders/AutofacServiceProviderFactory" w funkcji:

0  public IServiceProvider CreateServiceProvider(ContainerBuilder containerBuilder)
1  {
2      var container = containerBuilder.Build();
3
4      var workflowBuilder = new WatchmanBot(new DiscordConfiguration
5      {
6          MongoDbConnectionString = this._configuration.GetConnectionString("Mongo"),
7          Token = this._configuration["Discord:Token"]
8      }, container.Resolve<IComponentContext>()).GetWorkflowBuilder();
9
10     workflowBuilder.Build();
11     container.Resolve<HangfireJobsService>().SetDefaultJobs(container);
12     return new AutofacServiceProvider(container);
13 }

Za inicjalizację serwisów obsługiwanych przez HangFire odpowiada linia 11.

InitializationService

public async Task InitServer(DiscordServerContext server)
{ 
    Log.Information("Initializing server: {server}", server.ToJson());
    await this.MuteRoleInit(server);
    var lastInitDate = this.GetLastInitDate(server);
    await this.ReadServerMessagesHistory(server, lastInitDate);
    await this._cyclicStatisticsGeneratorService.GenerateStatsForDaysBefore(server, lastInitDate);
    await this.NotifyDomainAboutInit(server);
    Log.Information("Done server: {server}", server.ToJson());
}

InitializationService jest używany w klasie "WatchmanBot" w funkcji "GetWorkflowBuilder()". Metoda "InitServer" zajmuje się wszystkim co potrzebne aby dostać na podanym w argumencie serwerze gotowego do pracy bota.

private async Task MuteRoleInit(DiscordServerContext server)
{
    await this._muteRoleInitService.InitForServer(server);
    Log.Information("Mute role initialized: {server}", server.Name);
}

Tworzy na serwerze rolę "Muted" (wraz z ustawieniem odpowiednich uprawnień roli, np. brak możliwości wysyłania wiadomości), która może być nadana wybranej osobie przez bota za pomocą komendy "-mute" lub przez antispam.

private async Task ReadServerMessagesHistory(DiscordServerContext server, DateTime lastInitDate)
{
    foreach (var textChannel in server.GetTextChannels())
    {
        await this._serverScanningService.ScanChannelHistory(server, textChannel, lastInitDate);
    }
    Log.Information("Read messages history: {server}", server.Name);
}

Przeszukuje wszystkie kanały na danym serwerze i nowe (niezapisane jeszcze w bazie) wiadomości zapisuje do bazy danych bota.

private DateTime GetLastInitDate(DiscordServerContext server)
{
    var query = new GetInitEventsQuery(server.Id);
    var initEvents = this._queryBus.Execute(query).InitEvents.ToList();
    if (!initEvents.Any())
    {
        return DateTime.UnixEpoch;
    }

    var lastInitEvent = initEvents.Max(x => x.EndedAt);
    return lastInitEvent;
}

Każda inicjalizacja bota na danym serwerze zostawia o sobie informację w bazie danych, w tej metodzie szukamy ostatniej daty włączenia bota dla wybranego serwera.

public async Task GenerateStatsForDaysBefore(DiscordServerContext server, DateTime? lastInitDate)
{
    var dayStatisticsQuery = new GetServerDayStatisticsQuery(server.Id);
    var allServerDaysStatistics = (await this._queryBus.ExecuteAsync(dayStatisticsQuery)).ServerDayStatistics.ToList();
    var startDate = lastInitDate ?? DateTime.UnixEpoch;
    var messagesQuery = new GetMessagesQuery(server.Id)
    {
        SentDate = new TimeRange(startDate, DateTime.Today) // it will exclude today - it should generate today's stats tomorrow
    };
    var messages = this._queryBus.Execute(messagesQuery).Messages;

    var messagesNotCachedForStats = messages
        .Where(message => allServerDaysStatistics.All(s => message.SentAt.Date != s.Date));

    var serverStatistics = messagesNotCachedForStats
        .GroupBy(x => x.SentAt.Date)
        .Select(x => new ServerDayStatistic(x.ToList(), server.Id, x.Key));

    var commands = serverStatistics.Select(x => new AddServerDayStatisticCommand(x));
    foreach (var command in commands)
    {
        await this._commandBus.ExecuteAsync(command);
    }
}

ServerDayStatistic zawiera liczbę wiadomości na całym serwerze z danego dnia i statystyki kanałów - ChannelDayStatistic.
ChannelDayStatistic zawiera nazwę kanału i liczbę wiadomości na nim.
Powyższa funkcja tworzy i dodaje do bazy danych najnowsze statystyki na serwerze z poprzednich dni. (bez dzisiejszego)

private async Task NotifyDomainAboutInit(DiscordServerContext server)
{
    var command = new AddInitEventCommand(server.Id, endedAt: DateTime.UtcNow);
    await this._commandBus.ExecuteAsync(command);
}

Zapisanie w bazie danych informacji o udanym zainicjowalizowaniu bota na serwerze, więc teraz to będzie dla niego "LastInitDate".