Skip to content
Алексей . edited this page Jul 26, 2026 · 1 revision

Models

English Русский

Reference for every public model the library returns or accepts: log pages, account data, the filter catalogue, the admin-activity and top-operations reports, and the transport and configuration types.

Conventions

These rules hold for every model on this page.

  • All public models are sealed immutable records. The single exception is LogsParserRegistrationOptions, which is a sealed class with mutable properties because it is filled in by a configuration callback.
  • Collections are never null. A model that exposes an IReadOnlyList<T> always has one; when the source page carries nothing, the parser stores Array.Empty<T>(). Test for Count == 0, not for null.
  • Optional scalars are nullable. long?, string? and nullable model references mean "the page did not show this", not "zero" or "empty".
  • Records give you value equality, ToString(), Deconstruct and with. Positional parameters become public init-only properties with the same names.
  • Models do not validate. Constructing one never throws; the exceptions listed below come from the parsers and the transport that produce them.

Namespaces

Types Namespace
ParserRequest, ParserCookie LogsParser
Every other model on this page LogsParser.Models

Folder names in the repository do not match namespaces — Models/Shared/CommonModels.cs declares namespace LogsParser;, everything else under Models/ declares namespace LogsParser.Models;.

Where the models come from

Model Produced by Throws
LogsPage LogsParserClient.GetLogsAsync, LogsHtmlParser.ParseLogs ArgumentException on blank HTML; degrades to an empty Entries list when the table markup is missing
LogsAccount LogsParserClient.GetCurrentAccountAsync; also embedded in LogsPage.Account and LogsFilterCatalog.Account ArgumentException on blank HTML; null when the layout carries no account markup
LogsFilterCatalog LogsParserClient.GetLogsFilterCatalogAsync, LogsFilterCatalogParser.Parse ArgumentException on blank HTML
AdminActivityReport LogsParserClient.GetAdminActivityAsync, LogsHtmlParser.ParseAdminActivity ArgumentException on blank HTML; HtmlParsingException when the period inputs or the table are missing
TopOperationsReport LogsParserClient.GetTopOperationsAsync, LogsHtmlParser.ParseTopOperations ArgumentException on blank HTML; HtmlParsingException when the date or the table are missing

The query models LogsQuery, AdminActivityQuery and TopOperationsQuery are documented on Requests and URI Builder.

Logs

LogsPage

public sealed record LogsPage(
    IReadOnlyList<LogEntry> Entries,
    LogPageMetaInfo? MetaInfo = null,
    LogsAccount? Account = null);
Member Type Description
Entries IReadOnlyList<LogEntry> Rows of the log table in page order. Empty when the page has no <tbody>.
MetaInfo LogPageMetaInfo? Pagination counters, null when the page shows no pagination line.
Account LogsAccount? The account read from the page layout, null when the layout carries no account markup.
var page = await client.GetLogsAsync(new LogsQuery(ServerId: 18, Limit: 100));

Console.WriteLine($"{page.Entries.Count} entries of {page.MetaInfo?.Total} total");
Console.WriteLine($"signed in as {page.Account?.Nickname}");

LogEntry

public sealed record LogEntry(
    DateTime Timestamp,
    string Text,
    string Html,
    LogParticipant? Sender = null,
    LogParticipant? Target = null)
{
    public IReadOnlyList<LogRevealedValue> RevealedValues { get; init; } = Array.Empty<LogRevealedValue>();
}
Member Type Description
Timestamp DateTime The row's date and time, parsed with the invariant culture.
Text string The action text as the page visibly shows it.
Html string The complete inner markup of the action cell, hidden blocks included.
Sender LogParticipant? The "I" participant, null when the row has no data for it.
Target LogParticipant? The "II" participant, null when the row has no data for it.
RevealedValues IReadOnlyList<LogRevealedValue> Blocks the site hides behind its eye toggle. Empty when the row has none.

RevealedValues is an init-only property, not a positional parameter of the record. That is deliberate: adding a positional parameter would change the primary constructor arity and the generated Deconstruct of a record that is already published, so the property was added to the record body instead. The primary constructor still takes five arguments, and Deconstruct still yields (Timestamp, Text, Html, Sender, Target). Set the collection with an object initialiser:

var entry = new LogEntry(DateTime.UtcNow, "Готовый сет:", "<td>…</td>")
{
    RevealedValues = [new LogRevealedValue("Значение строки 1", "1.БоДжек Редкость 6(9553)", "…")]
};

var (timestamp, text, html, sender, target) = entry;

Text and Html are not two renderings of the same thing. Text holds only what the page visibly shows — content hidden behind the eye toggle is removed from it — while Html keeps the complete cell markup including the hidden blocks. For the row that carries a hidden set:

// Text stops at the visible part…
Console.WriteLine(entry.Text);   // Игрок Ivan_Kuznetsov арендует объявление №4851. Готовый сет:
// …while Html still contains the app__hidden block with the set itself.
Console.WriteLine(entry.Html.Contains("app__hidden"));  // True

Everything that was cut out of Text is available in a structured form through RevealedValues.

LogRevealedValue

public sealed record LogRevealedValue(
    string Label,
    string Text,
    string Html);
Member Type Description
Label string The block's data-title attribute, for example Значение строки 1. Empty string when the attribute is absent.
Text string The revealed value as text, with the line structure of the original block preserved.
Html string The raw inner markup of the block.

Revealed values appear most often under the item_market filter, where a row hides a full item set behind the eye toggle. Because Text keeps the line breaks, a set can be split back into its lines:

foreach (var value in entry.RevealedValues)
{
    Console.WriteLine(value.Label);   // Значение строки 1
    foreach (var line in value.Text.Split('\n', StringSplitOptions.RemoveEmptyEntries))
    {
        Console.WriteLine($"  {line.Trim()}");   // 1.БоДжек Редкость 6(9553), Описание: …, 2.ТВ Робот(8583), …
    }
}

LogParticipant

public sealed record LogParticipant(
    long? Money = null,
    long? Bank = null,
    long? Donate = null,
    LogAdditionalInfo? AdditionalInfo = null,
    string? LastIp = null,
    string? RegistrationIp = null);
Member Type Description
Money long? Cash on hand at the moment of the entry.
Bank long? Bank balance.
Donate long? Donate currency balance.
AdditionalInfo LogAdditionalInfo? The account block hidden behind the eye toggle in the data cell.
LastIp string? The last IP address shown in the IP cell.
RegistrationIp string? The registration IP address shown in the IP cell.

LogEntry.Sender is the "I" participant and LogEntry.Target is the "II" participant, exactly as the site marks them in the data cell. Each participant is bound to the additional-info block that follows its own marker, not paired with blocks by index, so a row that only has a target never has its data attributed to the sender:

// A row whose data cell starts with the "II:" marker only.
Console.WriteLine(entry.Sender is null);                       // True
Console.WriteLine(entry.Target?.AdditionalInfo?.AccountId);    // 999111

A participant is materialised only when the row carries money figures, an additional-info block or an IP for that marker; otherwise the property stays null.

LogAdditionalInfo

public sealed record LogAdditionalInfo(
    long AccountId,
    long VC,
    long SubAccount1,
    long SubAccount2,
    long SubAccount3,
    long SubAccount4,
    long SubAccount5,
    long SubAccount6,
    long Deposit,
    int AdminLevel);
Member Type Description
AccountId long Account identifier (ID аккаунта).
VC long VC$ balance.
SubAccount1 … SubAccount6 long The six additional accounts (Доп. счёт №1 … №6), taken from the page as-is. The site commonly renders -1 here; the library assigns no meaning to that value.
Deposit long Deposit balance.
AdminLevel int Administration level.

The block is parsed only when it exposes all ten values; an incomplete block yields null for LogParticipant.AdditionalInfo.

LogPageMetaInfo

public sealed record LogPageMetaInfo(int Start, int End, int Total);
Member Type Description
Start int Index of the first row on the page (1-based).
End int Index of the last row on the page.
Total int Total number of rows matching the query across all pages.

Read from the page's Показано с … по … из … line, which is present even when the table itself is not — a page with no <tbody> still returns meta info.

if (page.MetaInfo is { } meta)
{
    var pages = (int)Math.Ceiling(meta.Total / (double)(meta.End - meta.Start + 1));
    Console.WriteLine($"{meta.Start}-{meta.End} of {meta.Total} ({pages} pages)");
}

Account

LogsAccount

public sealed record LogsAccount(
    string Nickname,
    IReadOnlyList<LogsAccountBadge> Badges,
    IReadOnlyList<LogsAccountServer> AvailableServers);
Member Type Description
Nickname string The nickname shown in the navigation bar. Empty string when the markup is absent.
Badges IReadOnlyList<LogsAccountBadge> Badges next to the nickname, de-duplicated by name.
AvailableServers IReadOnlyList<LogsAccountServer> Servers offered by the server selector.

The whole model is null (not empty) when the layout carries no nickname, no badges and no server selector at all — that is the signal that the HTML is not a signed-in page.

LogsAccountBadge

public sealed record LogsAccountBadge(string Name);
Member Type Description
Name string Badge text, for example Admin or VIP.

LogsAccountServer

public sealed record LogsAccountServer(
    int Id,
    string Name,
    string DisplayName,
    bool IsSelected);
Member Type Description
Id int The server number used as LogsQuery.ServerId.
Name string The server name alone, with the numeric prefix stripped (Phoenix).
DisplayName string The label as rendered, including the numeric prefix ([18] Phoenix).
IsSelected bool true for the server currently selected in the session.

When the label does not follow the [n] Name shape, Name and DisplayName are identical.

var account = await client.GetCurrentAccountAsync();

var current = account?.AvailableServers.FirstOrDefault(server => server.IsSelected);
Console.WriteLine($"{current?.DisplayName} → ServerId {current?.Id}");   // [18] Phoenix → ServerId 18

var page = await client.GetLogsAsync(new LogsQuery(ServerId: current!.Id));

Filter catalogue

LogsFilterCatalog

public sealed record LogsFilterCatalog(
    IReadOnlyList<LogsFilterDefinition> Filters,
    LogsAccount? Account = null);
Member Type Description
Filters IReadOnlyList<LogsFilterDefinition> Every filter offered by the log type selector.
Account LogsAccount? The account read from the same page layout.

LogsFilterDefinition

public sealed record LogsFilterDefinition(
    string Code,
    string Name,
    IReadOnlyList<LogsFilterAdditionalParameter> AdditionalParameters);
Member Type Description
Code string The filter code sent to the service, for example kill or item_market. This is what goes into LogsQuery.Filters.
Name string The human-readable Russian label, for example Убийство.
AdditionalParameters IReadOnlyList<LogsFilterAdditionalParameter> Extra inputs the site shows for this filter. Empty when it has none.

LogsFilterAdditionalParameter

public sealed record LogsFilterAdditionalParameter(
    string QueryKey,
    string Label);
Member Type Description
QueryKey string The query-string key of the input, always of the form dynamic[n], for example dynamic[0].
Label string The input's label, for example Оружие.

QueryKey is exactly the key LogsQuery.AdditionalParameters accepts — the URI builder rejects keys that do not match ^dynamic\[\d+\]$:

var catalog = await client.GetLogsFilterCatalogAsync();
var kill = catalog.Filters.Single(filter => filter.Code == "kill");
var weapon = kill.AdditionalParameters.First();   // QueryKey "dynamic[0]", Label "Оружие"

var page = await client.GetLogsAsync(new LogsQuery(
    ServerId: 18,
    Filters: [kill.Code],
    AdditionalParameters: new Dictionary<string, string> { [weapon.QueryKey] = "Deagle" }));

Admin activity

AdminActivityReport

public sealed record AdminActivityReport(
    (DateTime From, DateTime To) Period,
    IReadOnlyList<AdminActivityEntry> Entries,
    AdminActivityMetaInfo MetaInfo);
Member Type Description
Period (DateTime From, DateTime To) The period the service actually reported, read back from the form inputs.
Entries IReadOnlyList<AdminActivityEntry> One entry per administrator.
MetaInfo AdminActivityMetaInfo Aggregate counters, never null.

Period is a named tuple, not a record — access it as report.Period.From / report.Period.To, or deconstruct it:

var report = await client.GetAdminActivityAsync(
    new AdminActivityQuery(DateTime.Today.AddDays(-7), DateTime.Today));

var (from, to) = report.Period;
Console.WriteLine($"{from:yyyy-MM-dd} — {to:yyyy-MM-dd}");

AdminActivityEntry

public sealed record AdminActivityEntry(
    string Nickname,
    int Id,
    int AdminLevel,
    int TotalReports,
    int TotalMutes,
    int TotalJails,
    int TotalWarns,
    int TotalBans,
    int TotalThanks,
    TimeSpan TotalOnline,
    IReadOnlyList<AdminActivityDay> Details);
Member Type Description
Nickname string Administrator's nickname.
Id int Administrator's identifier.
AdminLevel int Administration level.
TotalReports int Reports handled over the period.
TotalMutes int Mutes issued over the period.
TotalJails int Jails issued over the period.
TotalWarns int Warns issued over the period.
TotalBans int Bans issued over the period.
TotalThanks int Thanks received over the period.
TotalOnline TimeSpan Total time online.
Details IReadOnlyList<AdminActivityDay> Per-day breakdown. Empty when the row has no expandable block.

TotalOnline is not read from a cell of its own: it is the sum of the per-day Online values from Details. When Details is empty, TotalOnline is TimeSpan.Zero even if the administrator was online.

foreach (var admin in report.Entries.OrderByDescending(entry => entry.TotalOnline))
{
    Console.WriteLine($"{admin.Nickname} [{admin.AdminLevel}] {admin.TotalOnline:hh\\:mm\\:ss} " +
                      $"over {admin.Details.Count} days, {admin.TotalBans} bans");
}

AdminActivityDay

public sealed record AdminActivityDay(
    DateTime Date,
    TimeSpan Online,
    int Reports,
    int Mutes,
    int Jails,
    int Warns,
    int Bans);
Member Type Description
Date DateTime The day, parsed from yyyy-MM-dd.
Online TimeSpan Time online on that day (hh:mm:ss).
Reports int Reports handled that day.
Mutes int Mutes issued that day.
Jails int Jails issued that day.
Warns int Warns issued that day.
Bans int Bans issued that day.

AdminActivityMetaInfo

public sealed record AdminActivityMetaInfo(int AdminCount, int PeriodDays, int TotalReports, int TotalBans);
Member Type Description
AdminCount int Number of parsed entries.
PeriodDays int Whole days between Period.From and Period.To, never negative.
TotalReports int Sum of TotalReports over all entries.
TotalBans int Sum of TotalBans over all entries.

Every counter is computed from the parsed entries, not read from the page.

Top operations

TopOperationsReport

public sealed record TopOperationsReport(
    DateOnly Date,
    IReadOnlyList<TopOperationsEntry> Entries,
    TopOperationsMetaInfo MetaInfo);
Member Type Description
Date DateOnly The day the report covers, read from the page's Данные за: line.
Entries IReadOnlyList<TopOperationsEntry> One entry per player.
MetaInfo TopOperationsMetaInfo Aggregate counters, never null.

Date is a DateOnly — the report is per-day and carries no time component, unlike TopOperationsQuery.Date, which is a DateTime?.

TopOperationsEntry

public sealed record TopOperationsEntry(
    string Nickname,
    int Id,
    IPAddress Ip,
    IPAddress RegistrationIp,
    int TotalTransactions,
    ulong Sum);
Member Type Description
Nickname string Player's nickname.
Id int Player's identifier.
Ip IPAddress Last IP address.
RegistrationIp IPAddress Registration IP address.
TotalTransactions int Number of transactions on that day.
Sum ulong Total amount moved by the player.

Both IP members are System.Net.IPAddress (namespace System.Net), not strings — note the asymmetry with LogParticipant.LastIp / RegistrationIp, which are string?. An unparsable address falls back to IPAddress.Loopback and is logged as a warning.

var top = await client.GetTopOperationsAsync(new TopOperationsQuery(Date: DateTime.Today));

foreach (var entry in top.Entries.Where(e => e.Ip.Equals(e.RegistrationIp)))
{
    Console.WriteLine($"{entry.Nickname} ({entry.Id}): {entry.Sum:N0} in {entry.TotalTransactions} transactions");
}

TopOperationsMetaInfo

public sealed record TopOperationsMetaInfo(int PlayerCount, int TotalTransactions, ulong TotalSum);
Member Type Description
PlayerCount int Number of parsed entries.
TotalTransactions int Sum of TotalTransactions over all entries.
TotalSum ulong Sum of Sum over all entries, also ulong.

Transport and configuration

ParserRequest

public sealed record ParserRequest(string RelativeUri, ICookieStorage? CookieStorage = null);

Namespace LogsParser.

Member Type Description
RelativeUri string The path and query to request, relative to LogsParserHttpOptions.BaseUri.
CookieStorage ICookieStorage? Storage that overrides the data source's default for this one request. null uses the default.
using LogsParser;
using LogsParser.Abstractions;

ILogsParserDataSource dataSource = /* … */;
var html = await dataSource.GetContentAsync(new ParserRequest("/?server_number=18&limit=100"));

ParserCookie

public sealed record ParserCookie(string Name, string Value);

Namespace LogsParser.

Member Type Description
Name string Cookie name, compared with StringComparer.Ordinal.
Value string Cookie value.

A cookie is a name/value pair and nothing else — every Set-Cookie attribute (path, expiry, flags) is discarded by design. Session cookies (arizonarp_session, XSRF-TOKEN) and the anti-DDoS token R3ACTLB all live in the same flat store.

using LogsParser;
using LogsParser.Abstractions;

var storage = new MemoryCookieStorage();
storage.SetCookies([new ParserCookie("arizonarp_session", "restored-value")]);

LogsParserCredentials

public sealed record LogsParserCredentials(string Login, string Password, string TotpSecret);
Member Type Description
Login string Site login.
Password string Site password.
TotpSecret string The Base32 TOTP secret used to generate RFC 6238 codes for two-factor confirmation.

Credentials are optional for the data source: without them it fetches pages with whatever cookies it has and fails with an authentication exception when the service asks for a login. Passwords and secrets are never logged.

LogsParserHttpOptions

public sealed record LogsParserHttpOptions
Property Type Default Description
BaseUri Uri new("https://arizonarp.logsparser.info/") Base address for every request.
UserAgent string "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36" User-Agent header.
Accept string "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7" Accept header.
MaxRetryAttempts int 5 Upper bound on retries for transient failures. Anti-DDoS solves and waits for a known rate-limit reset do not consume an attempt.
WaitForRateLimitReset bool true On 429, wait until the reset moment and retry; false throws RateLimitExceededException instead.

All properties are init-only, so a modified copy is made with with:

using LogsParser.Models;
using LogsParser.Net;

var options = new LogsParserHttpOptions() with
{
    MaxRetryAttempts = 2,
    WaitForRateLimitReset = false
};

using var dataSource = new LogsParserHttpDataSource(options: options);

BaseUri, UserAgent and Accept are only applied to an HttpClient that does not already define them — an injected client keeps its own BaseAddress and headers.

LogsParserRegistrationOptions

public sealed class LogsParserRegistrationOptions

This one is a sealed class with mutable properties, not a record: it is handed to the Action<LogsParserRegistrationOptions> callback of AddLogsParser and filled in there.

Property Type Default Description
Credentials LogsParserCredentials? null Credentials registered as a singleton when set.
HttpOptions LogsParserHttpOptions new() Transport options registered as a singleton.
CookieStorageFactory Func<IServiceProvider, ICookieStorage>? null Replaces the default singleton MemoryCookieStorage registration.
DataSourceFactory Func<IServiceProvider, ILogsParserDataSource>? null Replaces the default transient LogsParserHttpDataSource registration.
using LogsParser.DependencyInjection;
using LogsParser.Models;

services.AddLogsParser(options =>
{
    options.Credentials = new LogsParserCredentials("my_login", "my_password", "BASE32SECRET");
    options.HttpOptions = new LogsParserHttpOptions { MaxRetryAttempts = 3 };
});

Worked example: walking a LogsPage

using LogsParser;
using LogsParser.Models;
using LogsParser.Net;

using var dataSource = new LogsParserHttpDataSource(
    credentials: new LogsParserCredentials("my_login", "my_password", "BASE32SECRET"));

var client = new LogsParserClient(dataSource);

var page = await client.GetLogsAsync(new LogsQuery(
    ServerId: 18,
    Filters: ["item_market"],
    PeriodFrom: DateTime.Today.AddDays(-1),
    PeriodTo: DateTime.Today,
    Limit: 100));

if (page.MetaInfo is { } meta)
{
    Console.WriteLine($"rows {meta.Start}–{meta.End} of {meta.Total}");
}

foreach (var entry in page.Entries)
{
    // Text is what the page shows; the hidden blocks are not in it.
    Console.WriteLine($"{entry.Timestamp:yyyy-MM-dd HH:mm:ss}  {entry.Text}");

    // "I" — the sender.
    if (entry.Sender is { } sender)
    {
        Console.WriteLine($"  I : money={sender.Money} bank={sender.Bank} donate={sender.Donate}");
        Console.WriteLine($"      ip={sender.LastIp} reg={sender.RegistrationIp}");

        if (sender.AdditionalInfo is { } info)
        {
            Console.WriteLine($"      account={info.AccountId} deposit={info.Deposit} adminLevel={info.AdminLevel}");
        }
    }

    // "II" — the target. Present on its own even when there is no sender.
    if (entry.Target is { } target)
    {
        Console.WriteLine($"  II: money={target.Money} account={target.AdditionalInfo?.AccountId}");
    }

    // Everything the eye toggle hides, with its label and its line structure.
    foreach (var revealed in entry.RevealedValues)
    {
        Console.WriteLine($"  [{revealed.Label}]");
        foreach (var line in revealed.Text.Split('\n'))
        {
            if (!string.IsNullOrWhiteSpace(line))
            {
                Console.WriteLine($"    {line.Trim()}");
            }
        }
    }
}

See also

Clone this wiki locally