# Η Umami.Net και ανίχνευση bot

# Εισαγωγή

Μου αρέσει το ' [Αναρτήθηκε από LOT](/blog/category/Umami) στο παρελθόν χρησιμοποιώντας το Umami για την ανάλυση σε ένα αυτο-- αναγνωρισμένο περιβάλλον και ακόμη και δημοσίευσε [Επόμενο άρθροΗ ΜΣΚ0Net Nuget pacakge](https://www.nuget.org/packages/Umami.Net/). Ωστόσο, είχα ένα πρόβλημα όπου ήθελα να παρακολουθήσω τους χρήστες του RSS feed μου για το ; αυτό το post πηγαίνει στο γιατί και πώς το λύσαμε για το ΜSK2

[TOC]

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

# Το πρόβλημα

Το πρόβλημα είναι ότι οι αναγνώστες RSS feed προσπαθούν να περάσουν *Χρήσιμο* Οι πράκτορες χρήσης όταν ζητούν το feed. Αυτό επιτρέπει **Συμμόρφωση** προμηθευτές για την παρακολούθηση του αριθμού των χρηστών και του τύπου χρηστών που καταναλώνουν το feed. Ωστόσο, αυτό σημαίνει επίσης ότι η Umami θα αναγνωρίσει αυτές τις αιτήσεις ως *Μπότ* αιτήσεις. Αυτό είναι ένα ζήτημα για τη χρήση μου, καθώς προκύπτει ότι η αίτηση αγνοείται και δεν παρακολουθείταιΜSK1

Ο πράκτορας χρήστη του Feedbin μοιάζει με αυτό:

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

Έτσι αρκετά χρήσιμο σωστά, θα περάσει μερικές χρήσιμες λεπτομέρειες σχετικά με το τι το ID feed σας είναιM SK1 τον αριθμό των χρηστών και ο πράκτορας του χρήστη. ΩστόσοMSKA3 αυτό είναι επίσης ένα πρόβλημα καθώς σημαίνει ότι η Umami θα αγνοήσει την αίτησηMKA4 στην πραγματικότητα θα επιστρέψει μια Mska6 κατάσταση BUT το περιεχόμενο περιέχει `{"beep": "boop"}` Σημαίνει ότι αυτό αναγνωρίζεται ως αίτημα bot. Αυτό είναι ενοχλητικό καθώς μπορώ να κάνω το 't χειριστεί αυτό μέσω της κανονικής επεξεργασίας σφαλμάτων M SK2t't ένα ΜSK4t μη λένε μια ΜSK5t κλπ

Όταν κοιτάζουμε το Umami Dashboard μετά από αυτή την αλλαγή μπορώ τώρα να δω τα αρχικά UserAgents που ζήτησαν τη σύνδεση:

![Ετικέτες Dashboard](user-agents.png?width=600&format=webp&quality=30)

Έτσι μπορούμε να δούμε ότι το ' αναφέρει τώρα μια σειρά που θα είχε προκαλέσει την ανίχνευση bot, αλλά μπορούμε τώρα να τα δούμε και να παρακολουθήσουμε τις αιτήσεις του . αναφέρουν

# Η λύση

Έτσι τι' ικανοποιεί τη λύση για αυτό? μπορώ να μετρήσω το ' ετοιμάζει χειροκίνητα όλα αυτά τα αιτήματα και αν ανακαλύψει εάν το Umami θα τα ανιχνεύσει ως ένα botΜSK3 χρησιμοποιεί IsBot M SK4httpsMK5wwwMST6npmjs.comMSC8packageMSM9isbotMSS10 για να διαπιστώσει αν μια αίτηση είναι ένα bot ή όχι. Υπάρχει το ' δεν CMSKA13 ισοδύναμο και αυτόΜΣΚ14σε μια μεταβαλλόμενη λίστα έτσι μπορώ επίσης να κάνω χρήση της λίστας ότι ( μελλον
Έτσι πρέπει να διακόψω το αίτημα πριν φτάσει στην Umami και να αλλάξω τον Χρήστη σε κάτι που η Umami θα αποδεχτεί για συγκεκριμένες αιτήσεις.

Έτσι τώρα έχω προσθέσει κάποιες πρόσθετες παραμέτρους στις μεθόδους παρακολούθησης μου στο .Net.Αυτές σας επιτρέπουν να καθορίσετε το νέο 'Ο προεπιλεγμένος χρήστης πράκτορας του ' θα αποσταλεί στο Umami αντί για τον αρχικό Χρήστη Πράκτορα της .Αυτό μου επιτρέπει να ορίσω ότι ο Χρήστης πράκτης θα πρέπει να αλλάξει σε μια συγκεκριμένη τιμή για συγκεκριμένες αιτήσεις για την .

## Οι μέθοδοι

ΣΤΟ ΜΟΥ `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` αντικείμενο.Αυτό το αντικείμενο περιέχει αποκρυπτογραφημένο token JWT ΜSK1που είναι αναποτελεσματικό εάν' διατηρεί ένα ρομπότ, οπότε αυτό είναι χρήσιμο για να ελέγξετεM SK3 και την κατάσταση της αίτησηςMK4

## `PayloadService`

Όλα αυτά αντιμετωπίζονται στο `Payload` Υπηρεσία που είναι υπεύθυνη για τον πληθυσμό του αντικειμένου επιβάρυνσης. Αυτό είναι όπου το `UseDefaultUserAgent` ΕΠΙΤΡΟΦΟΡΕΥΘΗΚΕ ΤΟ ΜΣΚ0

Με προεπιλογή, πληρώνω το φορτίο από την `HttpContext` Έτσι, συνήθως παίρνετε αυτό το σύνολο σωστά;Θα μου άρεσε'θα δείξει αργότερα πού αυτό τραβήχτηκε πίσω από το UmamiΜSK2

```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` ποιος είναι ο τόπος όπου το αντικείμενο αίτησης λαμβάνει αυτό' αναφέρει τα δεδομένα που εγκαθίστανταιΜSK1 αναφέρουν

```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). Στη συνέχεια, με τη μέθοδο που εντοπίζει εάν είτε ο ΧρήστηςAgent είναι μηδενικό M SK1which shouldn't συμβεί εκτός και αν το ' καλεί από κώδικα χωρίς ένα HttpContextΜSK4 ή αν η `UseDefaultUserAgent` ρυθμίζεται. Εάν είναι τότε τοποθετεί το UserAgent στην προεπιλογή και προσθέτει τον αρχικό UserAgents στο αντικείμενο δεδομένων.

Αυτό είναι στη συνέχεια συνδεθεί ώστε να μπορείτε να δείτε τι UserAgent χρησιμοποιείται.

## Αποκρυπτογράφηση της απάντησης.

Στο Umami.Net 0.3.0Έχω προσθέσει μια σειρά από νέες ΜSK2AndDecodeM SK3 μεθόδους που επιστρέφουν ένα `UmamiDataResponse` object. Αυτό το αντικείμενο περιέχει τον αποκωδικοποιημένο token 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` γραμμές ( για ανίχνευση botΜSK1 Αν τα βρει τότε καταγράφει μια προειδοποίηση και επιστρέφει ένα `BotDetected` status. Εάν δεν ενδιαφέρεται' αναφέρει να τα βρει τότε αποσυνδέει το token JWT και επιστρέφει τον φόρτο πληρωμής.

Το ίδιο το token του JWT είναι μόνο μια γραμμή κρυπτογράφησης Base64 που περιέχει τα δεδομένα που έχει αποθηκεύσει η Umami. Αυτό αποκωδικοποιείται και επιστρέφεται ως `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>
Μπορείτε να δείτε ότι αυτό περιέχει μια δέσμη χρήσιμων πληροφοριών σχετικά με το αίτημα που έχει αποθηκεύσει η Umami.Αν θέλετε για παράδειγμα να εμφανίσετε διαφορετικό περιεχόμενο βασισμένο σε local, γλώσσαΜSK2 πρόγραμμα περιήγησης κλπ. Αυτό σας επιτρέπει να το κάνετε

```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 0.4.0 η οποία σας επιτρέπει να προσδιορίσετε έναν προεπιλεγμένο Χρήστη για συγκεκριμένες αιτήσειςΜSK2 Αυτό είναι χρήσιμο για την παρακολούθηση των αιτήσεων που το Umami θα αγνοούσε διαφορετικά.