# Umami.Netti ja Bot-detection

# Esitys

Näin ollen [posted a LOT](/blog/category/Umami) aiemmin Umamia käytetään analyyttisesti itsenäisessä ”-” -ympäristössä ja jopa julkaistiin [Umami.Net Nuget pacakge](https://www.nuget.org/packages/Umami.Net/). Minulla oli kuitenkin ongelma, jossa halusin seurata RSS-kanavani käyttäjiä.

[TOC]

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

# Ongelma

Ongelma on siinä, että RSS-kanavan käyttäjät yrittävät välittää *hyödyllinen* Käyttäjäagentit pyydettäessä reittiä. Tämä sallii **soveltuva** liikenteenharjoittajien on seurattava niiden käyttäjien määrää ja niitä käyttäjiä, jotka kuluttavat reittiä. Tämä merkitsee kuitenkin myös sitä, että Umami tunnistaa nämä pyynnöt *botti* pyynnöt. Tämä on minulle käyttökelpoinen kysymys, koska sen seurauksena pyyntö jätetään huomiotta eikä jäljitetä.

Feedbinin käyttäjäagentti näyttää tällaiselta:

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

Tämä on myös ongelma, koska se tarkoittaa sitä, että Umami jättää pyynnön huomiotta. `{"beep": "boop"}` meaning that this is identified as a bot request. This is annoying as I can't handle this through normal error handling M SK2it\'s a {\displaystyle {\mathrm {MSK , eikä sanota \muun muassa \

Kun tarkastelemme Umami Dashboardia tämän muutoksen jälkeen, voin nyt nähdä alkuperäiset UserAgentsit, jotka ovat pyytäneet reittiä:

![Umami Dashboard](user-agents.png?width=600&format=webp&quality=30)

Voimme siis nähdä, että ' on nyt joukko, joka olisi herättänyt robotin havaitsemisen, mutta voimme nyt nähdä nämä ja seurata pyyntöjä.

# Ratkaisu

Mikä on tämän ratkaisu??. Voin'. En voi käsitellä kaikkia näitä pyyntöjä ja havaita, havaitseeko Umami havaitsee ne bottina? ;. Se käyttää IsBotia.
Minun on siis pysäytettävä pyyntö ennen kuin se pääsee Umamiin ja muutettava käyttäjäagentti johonkin asiaan, jonka Umami hyväksyy tiettyjen pyyntöjen osalta.

Näin ollen olen nyt lisännyt joitakin lisäparametereja omiin jäljitettämismenetelmiini Umamissa.Net. Näiden avulla voitte täsmentää, että uusi \'Default User AgentM SK3 lähetetään Umamiin alkuperäisen User Agentin sijasta.

## Menetelmät

Minun `UmamiBackgroundSender` Lisäsin seuraavaa:

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

Tämä on olemassa kaikissa jäljitettävissä menetelmissä ja vain asettaa parametrin `UmamiPayload` object.

Otetaan käyttöön `UmamiClient` ne voidaan asettaa seuraavasti:

```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);
    }
```

Tässä testissä käytän uutta `TrackPageViewAndDecode` menetelmä, joka palauttaa `UmamiDataResponse` object. Objektissa on dekodioitu JWT-tokeni (, joka ei ole pätevä, jos se on botti, joten tämä on hyödyllistä tarkistaa

## `PayloadService`

Tämä kaikki käsitellään `Payload` Palvelu, joka on vastuussa maksukannan sisällön määrittelystä. `UseDefaultUserAgent` on asetettu.

Standardin mukaan populaoin maksukantaa `HttpContext` Näin ollen saatte tavallisesti tämän järjestelmän oikein; IM SK1 osoitan myöhemmin, missä se on vetäytynyt Umamista.

```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;
    }
```

Sitten minulla on eräs koodi, joka kutsutaan `PopulateFromPayload` jossa pyynnön kohde saa sen

```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);
     }        
        
```

Huomaatte, että tämä määrittelee uuden käyttäjäagentin tiedoston ytimessä (, jonka olen vahvistanut olevan *tällä hetkellä* havaitaan botina). Sen jälkeen menetelmässä se havaitsee, onko joko UserAgent null (joka ei pitäisiM SK2 tapahtua, ellei se 's kutsu kodesta ilman HttpContextia `UseDefaultUserAgent` on asetettu. Jos se on, se asettaa UserAgentin standardiksi ja lisää alkuperäisen UserAgentin tietoobjektiin.

Tämä on sitten tallennettu, jotta voitte nähdä, mitä UserAgentia käytetään.

## Vastauksen dekodiointi.

'AndDecode' -menetelmät, jotka palauttavat `UmamiDataResponse` väline. Tämä väline sisältää dekoditun JWT-tokenin

```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;
        }
    }
```

Voitte nähdä, että tämä kutsuu normaaliin `TrackPageView` menetelmä kutsuu sen jälkeen `DecodeResponse` joka tarkistaa vastauksen `beep` ja `boop` strings (bot-tutkimukseenM SK1 Jos se löytää ne, se kirjaa varoituksen ja palauttaa `BotDetected` status. Jos se ei löydä niitä, se dekoditsee JWT-tokenin ja palauttaa maksukannan

JWT-token itse on vain Base64 kodoitu string, joka sisältää Umamin tallettamat tiedot. `UmamiDataResponse` object.

Kokonaislähde tästä on jäljempänä:

<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>
Voitte nähdä, että tässä on paljon hyödyllistä tietoa Umamin tallennetusta pyynnöstä. Jos haluatte esimerkiksi näyttää erilaista sisältöä paikalliseen järjestelmään perustuvanaM SK1 kieleen, hakukone jne., tämä antaa teille mahdollisuuden tehdä senMSC3

```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; }
```

# Päätelmissä

Joten vain lyhyt artikkeli, jossa käsitellään Umamin uusia toimintoja.Net 0.4.0, jonka avulla voitte määritellä tavanomaisen käyttäjäagentin tietyistä pyynnöistä. Tämä on hyödyllistä sellaisten pyyntöjen jäljittämiseksi, joita Umami muutoin sivuuttaa