Back to "Umami.Detection Net and Bot"

This is a viewer only at the moment see the article on how this works.

To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk

This is a preview from the server running through my markdig pipeline

ASP.NET Umami

Umami.Detection Net and Bot

Thursday, 12 September 2024

Введение

Отже, я відкрила LOT в минулому про використання Умамі для аналізу у самоobsługiwaному середовищі "-", і навіть опублікували Umami.Net Nuget pacakge. Однак у мене виникла проблема, коли я хотів відстежити користувачів мого RSS feedу ; цей пост йде до того, чому і як я його розв 'язав

Проблема

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

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

Feedbin feed-id:1234 - 21 subscribers

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

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

Дашборд Умамі

Отже, ми можемо побачити, що зараз - МСК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 Я додала наступне:

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

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

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

    [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 я покажу пізніше, де він повертається з Умамі.

    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 де об 'єкт отримує дані

    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

    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 об 'єкт.

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

Response Decoder
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
        };
    }
}
Ви бачите, що це містить купу корисної інформації про запрошення, яке накопичив Умамі. Якщо ви хочете, наприклад, показати інший контент, заснований на локальному положенніM SK1 мові , браузері і т.д. це дозволяє вам це зробити.
    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 що дозволяє вам визначити початковий Пользователь-Агент для конкретних запитів . Це корисно для відстеження запитів, які б інакше ігнорували Умами.

logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.