# Umami.Detection Net and Bot

# Введение

Отже, я [відкрила LOT](/blog/category/Umami) в минулому про використання Умамі для аналізу у самоobsługiwaному середовищі "-", і навіть опублікували [Umami.Net Nuget pacakge](https://www.nuget.org/packages/Umami.Net/). Однак у мене виникла проблема, коли я хотів відстежити користувачів мого RSS feedу ; цей пост йде до того, чому і як я його розв 'язав

[TOC]

<!--category-- ASP.NET, Umami -->
<datetime class="hidden">2024-09-12T14:50</datetime>

# Проблема

Проблема в тому, що читачі RSS *корисне* Агенти-пользователи, коли просять fed. Це дозволяє **підпорядкований** постачальники, щоб відстежити кількість користувачів і тип користувачів, які споживають канал feed . *робот* запити. Це проблема для мого використання, оскільки це призводить до того, що запити ігнорують та не відслідковуються

Агент користувача Feedbin виглядає ось так:

```plaintext
Feedbin feed-id:1234 - 21 subscribers
```

Так що досить корисне право, це передає деякі корисні деталі про те, який ваш id для каналу , кількість користувачів і користувачевий агент МSK2 Тим не менше МСК3 це також проблема, тому що це означає, що Умамі буде ігнорувати прохання ММСК4 насправді це МПСК5 поверне статус МУСК6, але контент містить `{"beep": "boop"}` що означає, що це ідентифіковане як робота-запрос. Це неприємно, бо я не можу з цим справитись через звичайне управляння помилками M SK2 це ' це МSK4 не кажучи МSK5 і т.д.

Коли ми дивимося на панель дошків Умамі після того, як зробили цю зміну, я тепер бачу оригінал UserAgents, який попросив fed:

![Дашборд Умамі](user-agents.png?width=600&format=webp&quality=30)

Отже, ми можемо побачити, що зараз - МСК0 - це група, яка могла б викликати розпізнавання робота, але тепер ми можемо бачити ці і відслідковувати запити.

# Рішення

Отже, що це за рішення для цього? МSK1. Я можу. МSK2. Я не можу ручно аналізувати всі ці прохання і виявити, чи Умамі визнає їх як бота. МСК3. Він використовує IsBot. МСК4. https. MСК5. www. М СК6. npmjs. M СК7. com. M S К8. пакет. М S К9. ISBOT. М סК10. Я зможу визначити, чи є ця прохання ботом, чи ні. МS К11. Там немає C. М. СК13. Це еквівалентно, а М С К14. Це змінний список, тож я можу навіть не використовувати цей список.
Тому я мушу перехопити запит перед тим, як він дістається до Умамі і змінити Агента користувача на щось, що Умами прийме для конкретних запитів.

Тож тепер я додав до своїх методик відстеження декілька додаткових параметрів в Умамі.NetM SK1 Вони дають вам змогу визначити, що новий 'Default User Agent' буде відправлено до Умами замість оригінального User Agent\. Це дозволяє мені визначити, чи мусить бути змінений User Agent на конкретну ціну для конкретних запитів

## Методи

На моєму `UmamiBackgroundSender` Я додала наступне:

```csharp
   public async Task TrackPageView(string url, string title, UmamiPayload? payload = null,
        UmamiEventData? eventData = null, bool useDefaultUserAgent = false)
```

Це існує на всіх методах відстеження і просто встановлює параметр на `UmamiPayload` об 'єкт.

На `UmamiClient` їх можна налаштувати так::

```csharp
    [Fact]
    public async Task TrackPageView_WithUrl()
    {
        var handler = JwtResponseHandler.Create();
        var umamiClient = GetServices(handler);
        var response = await umamiClient.TrackPageViewAndDecode("https://example.com", "Example Page",
            new UmamiPayload { UseDefaultUserAgent = true });
        Assert.NotNull(response);
        Assert.Equal(UmamiDataResponse.ResponseStatus.Success, response.Status);
    }
```

В цьому тесті я використовую новий `TrackPageViewAndDecode` метод, який повертає `UmamiDataResponse` об 'єкт. Цей об' єкт містить декодований символ JWT M SK1, який є непотрібним, якщо це МSK2 ботан, то це корисно для перевірки МSK3 та статусу запиту .

## `PayloadService`

Це все відбувається в `Payload` Служба, яка відповідає за наповнення об 'єктом вантажу. Тут `UseDefaultUserAgent` встановлено.

По замовчуванню я заповнюю payload з `HttpContext` так що ви зазвичай отримуєте цей набір правильно; IM SK1 я покажу пізніше, де він повертається з Умамі.

```csharp
    private UmamiPayload GetPayload(string? url = null, UmamiEventData? data = null)
    {
        var httpContext = httpContextAccessor.HttpContext;
        var request = httpContext?.Request;

        var payload = new UmamiPayload
        {
            Website = settings.WebsiteId,
            Data = data,
            Url = url ?? httpContext?.Request?.Path.Value,
            IpAddress = httpContext?.Connection?.RemoteIpAddress?.ToString(),
            UserAgent = request?.Headers["User-Agent"].FirstOrDefault(),
            Referrer = request?.Headers["Referer"].FirstOrDefault(),
            Hostname = request?.Host.Host
        };

        return payload;
    }
```

Тоді я отримав частину коду під назвою `PopulateFromPayload` де об 'єкт отримує дані

```csharp
    public static string DefaultUserAgent =>
        $"Mozilla/5.0 (Windows 11)  Umami.Net/{Assembly.GetAssembly(typeof(UmamiClient))!.GetName().Version}";

    public UmamiPayload PopulateFromPayload(UmamiPayload? payload, UmamiEventData? data)
    {
        var newPayload = GetPayload(data: data);
        ...
        
        newPayload.UserAgent = payload.UserAgent ?? DefaultUserAgent;

        if (payload.UseDefaultUserAgent)
        {
            var userData = newPayload.Data ?? new UmamiEventData();
            userData.TryAdd("OriginalUserAgent", newPayload.UserAgent ?? "");
            newPayload.UserAgent = DefaultUserAgent;
            newPayload.Data = userData;
        }


        logger.LogInformation("Using UserAgent: {UserAgent}", newPayload.UserAgent);
     }        
        
```

Ви побачите, що це визначає новий Пользователь на вершині файлу (, який я підтвердив *на даний момент* відкрили як Bot). Тоді в методі він визначає, чи є якийсь UserAgent нульовим (, який не повинен МSK2 статися допоки це МSK3 не буде викликано з коду без HttpContext) або якщо `UseDefaultUserAgent` встановлений. Якщо це так, то він встановлює UserAgent default і додає оригінального UserAgenта до об 'єкта даних

Потім це зареєструється, щоб ви могли бачити, що використовує UserAgent.

## Розшифровування відповіді.

У Umami.Net M SK1 я додав декілька нових 'AndDecode' методів, які повертають `UmamiDataResponse` об 'єкт. Цей об' єкт містить декодований символ JWT

```csharp
    public async Task<UmamiDataResponse?> TrackPageViewAndDecode(
        string? url = "",
        string? title = "",
        UmamiPayload? payload = null,
        UmamiEventData? eventData = null)
    {
        var response = await TrackPageView(url, title, payload, eventData);
        return await DecodeResponse(response);
    }
    
        private async Task<UmamiDataResponse?> DecodeResponse(HttpResponseMessage responseMessage)
    {
        var responseString = await responseMessage.Content.ReadAsStringAsync();

        switch (responseMessage.IsSuccessStatusCode)
        {
            case false:
                return new UmamiDataResponse(UmamiDataResponse.ResponseStatus.Failed);
            case true when responseString.Contains("beep") && responseString.Contains("boop"):
                logger.LogWarning("Bot detected data not stored in Umami");
                return new UmamiDataResponse(UmamiDataResponse.ResponseStatus.BotDetected);

            case true:
                var decoded = await jwtDecoder.DecodeResponse(responseString);
                if (decoded == null)
                {
                    logger.LogError("Failed to decode response from Umami");
                    return null;
                }

                var payload = UmamiDataResponse.Decode(decoded);

                return payload;
        }
    }
```

Ви бачите, що це змушує `TrackPageView` метод потім називає метод `DecodeResponse` який перевіряє відповідь на `beep` і `boop` strings (for bot detection). If it finds them then it logs a warning and returns a `BotDetected` status. Якщо він не знаходить їх, то декодує символ JWT і повертає навантаження

Токен JWT сам по собі - це лише кодований строк із базою64, який містить дані, які накопичив Умамі . Це декодовано і повертається як `UmamiDataResponse` об 'єкт.

Полне джерело для цього є внизу:

<details>
<summary>Response Decoder</summary>

```csharp
using System.IdentityModel.Tokens.Jwt;

namespace Umami.Net.Models;

public class UmamiDataResponse
{
    public enum ResponseStatus
    {
        Failed,
        BotDetected,
        Success
    }

    public UmamiDataResponse(ResponseStatus status)
    {
        Status = status;
    }

    public ResponseStatus Status { get; set; }

    public Guid Id { get; set; }
    public Guid WebsiteId { get; set; }
    public string? Hostname { get; set; }
    public string? Browser { get; set; }
    public string? Os { get; set; }
    public string? Device { get; set; }
    public string? Screen { get; set; }
    public string? Language { get; set; }
    public string? Country { get; set; }
    public string? Subdivision1 { get; set; }
    public string? Subdivision2 { get; set; }
    public string? City { get; set; }
    public DateTime CreatedAt { get; set; }
    public Guid VisitId { get; set; }
    public long Iat { get; set; }

    public static UmamiDataResponse Decode(JwtPayload? payload)
    {
        if (payload == null) return new UmamiDataResponse(ResponseStatus.Failed);
        payload.TryGetValue("visitId", out var visitIdObj);
        payload.TryGetValue("iat", out var iatObj);
        //This should only happen then the payload is dummy.
        if (payload.Count == 2)
        {
            var visitId = visitIdObj != null ? Guid.Parse(visitIdObj.ToString()!) : Guid.Empty;
            var iat = iatObj != null ? long.Parse(iatObj.ToString()!) : 0;

            return new UmamiDataResponse(ResponseStatus.Success)
            {
                VisitId = visitId,
                Iat = iat
            };
        }

        payload.TryGetValue("id", out var idObj);
        payload.TryGetValue("websiteId", out var websiteIdObj);
        payload.TryGetValue("hostname", out var hostnameObj);
        payload.TryGetValue("browser", out var browserObj);
        payload.TryGetValue("os", out var osObj);
        payload.TryGetValue("device", out var deviceObj);
        payload.TryGetValue("screen", out var screenObj);
        payload.TryGetValue("language", out var languageObj);
        payload.TryGetValue("country", out var countryObj);
        payload.TryGetValue("subdivision1", out var subdivision1Obj);
        payload.TryGetValue("subdivision2", out var subdivision2Obj);
        payload.TryGetValue("city", out var cityObj);
        payload.TryGetValue("createdAt", out var createdAtObj);

        return new UmamiDataResponse(ResponseStatus.Success)
        {
            Id = idObj != null ? Guid.Parse(idObj.ToString()!) : Guid.Empty,
            WebsiteId = websiteIdObj != null ? Guid.Parse(websiteIdObj.ToString()!) : Guid.Empty,
            Hostname = hostnameObj?.ToString(),
            Browser = browserObj?.ToString(),
            Os = osObj?.ToString(),
            Device = deviceObj?.ToString(),
            Screen = screenObj?.ToString(),
            Language = languageObj?.ToString(),
            Country = countryObj?.ToString(),
            Subdivision1 = subdivision1Obj?.ToString(),
            Subdivision2 = subdivision2Obj?.ToString(),
            City = cityObj?.ToString(),
            CreatedAt = createdAtObj != null ? DateTime.Parse(createdAtObj.ToString()!) : DateTime.MinValue,
            VisitId = visitIdObj != null ? Guid.Parse(visitIdObj.ToString()!) : Guid.Empty,
            Iat = iatObj != null ? long.Parse(iatObj.ToString()!) : 0
        };
    }
}
```

</details>
Ви бачите, що це містить купу корисної інформації про запрошення, яке накопичив Умамі. Якщо ви хочете, наприклад, показати інший контент, заснований на локальному положенніM SK1 мові , браузері і т.д. це дозволяє вам це зробити.

```csharp
    public Guid Id { get; set; }
    public Guid WebsiteId { get; set; }
    public string? Hostname { get; set; }
    public string? Browser { get; set; }
    public string? Os { get; set; }
    public string? Device { get; set; }
    public string? Screen { get; set; }
    public string? Language { get; set; }
    public string? Country { get; set; }
    public string? Subdivision1 { get; set; }
    public string? Subdivision2 { get; set; }
    public string? City { get; set; }
    public DateTime CreatedAt { get; set; }
    public Guid VisitId { get; set; }
    public long Iat { get; set; }
```

# На завершення

Отже, це лише коротенька стаття, яка охоплює деякі нові функції в Умамі.Net M SK1 що дозволяє вам визначити початковий Пользователь-Агент для конкретних запитів . Це корисно для відстеження запитів, які б інакше ігнорували Умами.