Back to "امامي.كشف الشبكة والبوت"

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

امامي.كشف الشبكة والبوت

Thursday, 12 September 2024

المقدمة

إذاً نشرت LOT في الماضي عن استخدام الأمامي للتحليلات في بيئة ذاتية - و حتى نشر Umami.Net Nuget pacakge. ومع ذلك كان لدي مشكلة حيث أردت أن أتابع مستخدمي تغريدات الراديو الخاصة بي ; هذا пост يذهب إلى السبب وكيف حلته

المشكلة

المشكلة هي أن قراءات RSS تحاول المرور مفيدة الوكالات المستخدمة عند طلب المغذيات. هذا يسمح متوافق المقدمين لمتابعة عدد المستخدمين و نوع المستخدمين الذين يستهلكون البث. ومع ذلك, هذا يعني أيضاً أن أوامري سوف يحدد هذه الطلبات الروبوت الطلبات. هذه مشكلة لاستعمالي لأنها تؤدي إلى تجاهل الطلب وليست تتبعه

الوكالة المستخدمة في "فيدبين" تبدو مثل هذا

Feedbin feed-id:1234 - 21 subscribers

اذاً مفيد جداً حقاً , ينقل بعض التفاصيل المفيدة حول ما هو id تغريدك , عدد المستخدمين والوكالة المستخدمة {"beep": "boop"} بمعنى أن هذا هو محدد كطلب بوت . هذا مزعج لأنني لا أستطيع ' التعامل مع هذا من خلال معالجة الأخطاء العادية | ( |it |' |s |200, |don't say | 403 |etc |

عندما ننظر إلى لوحة امامي بعد أن قمنا بتغيير هذا يمكنني الآن أن أرى الموظفين المستخدمين الأصليين الذين طلبوا غذاء :

لوحة اللوحة Umami

إذن يمكننا أن نرى هناك ' الآن مجموعة التي كانت قد triggered the bot detection but we can now see these AND track the requests .

الحل

إذن ما هو الحل لهذا؟ ? أستطيع ' أن أقوم بتحليل كل هذه الطلبات لاسيًا وأن أكتشف إذا كان أومامي سيكشف عنها كبوت؟ لذا يجب أن أختطف الطلب قبل أن يصل إلى أمامي وأقوم بتغيير عامل المستخدم إلى شيء سوف يقبل أمامي طلبات محددة

لذلك الآن قمت بإضافة بعض العوامل الإضافية إلى أساليب التتبع الخاصة بي في Umami.Net. هذه تسمح لك بالتحديد بأن يتم إرسال الـ"أعميل المستخدم العادي" M SK2أعيار الـ "أعمال المستخدم العادية" |' إلى Umami بدلاً من الأعمال الأصلية.

الطرق

على UmamiBackgroundSender أضافت التالي:

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

هذا موجود على جميع طرق التعقب هناك و يضع فقط متغيراً في UmamiPayload object.

على 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 SK3 وحالة الطلب

PayloadService

كل هذا يتم التعامل معه في Payload خدمة مسؤولة عن تكثيف جسم الشحنة. UseDefaultUserAgent هو قائم.

بالលំនាំដើម أقوم بجمع الشحنات من HttpContext لذا فإنك غالباً تحصل على هذه المجموعة بشكل صحيح; I' سأريكم لاحقاً أين يتم إخراجها من Umami

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

ستلاحظون أن هذا يحدد عاملاً مستخدماً جديداً في الجزء العلوي من الملف ( والذي قمت بتأكده هو حالياً تم إكتشافها كبوت ). ثم في الطريقة تكشف ما إذا كان الوكالة المستخدمة صفرا ( والذي لا ينبغي أن يحدث ' إلا إذا كان ' يتصل من البرمجة دون سياق Http UseDefaultUserAgent هو مكتوب . إذا كان كذلك، فإنه يضع الـUserAgent بالលំនាំដើម ويضيف الـ UserAgent الأصلي إلى جسم البيانات .

ومن ثم يتم تسجيل هذا بحيث يمكنك رؤية ما يستخدمه "إستخدم العميل".

فك شفرة الاستجابة.

في Umami.Net 0.3.0 قمت بإضافة عدد من الطرق الجديدة 'AndDecodeM SK3 التي تعيد 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 تسلسلات (لكشف البوت). إذا وجدها عندها يسجل إشارة ويعيد BotDetected حالة. إذا لم يجدها ' فإنه يفك شفرة टोकن JWT ويعيد الشحنة

تشيرة JWT نفسها هي عبارة عن مجرد شفرة قاعدة 64 تحتوي على البيانات التي تم تخزينها من قبل امامي . هذا يتم فك الشفرة ويعاد كـ UmamiDataResponse object.

المصدر الكامل لهذا هو :

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
        };
    }
}
يمكنكم رؤية أن هذا يحتوي على مجموعة من المعلومات المفيدة حول الطلب الذي تم تخزينه "أمامي"
    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; }

في الختام

لذا فهذه رسالة قصيرة تغطي بعض الوظائف الجديدة في Umami.Net 0.4.0 التي تسمح لك بتحديد عامل مستخدم تلقائي لطلبات محددةM SK2 هذا مفيد لمتابعة الطلبات التي يتجاهلها Umami بطريقة أخرى

logo

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