-
Notifications
You must be signed in to change notification settings - Fork 0
Models
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.
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 anIReadOnlyList<T>always has one; when the source page carries nothing, the parser storesArray.Empty<T>(). Test forCount == 0, not fornull. -
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(),Deconstructandwith. 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.
| 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;.
| 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.
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}");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")); // TrueEverything that was cut out of Text is available in a structured form through RevealedValues.
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), …
}
}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); // 999111A 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.
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.
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)");
}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.
public sealed record LogsAccountBadge(string Name);| Member | Type | Description |
|---|---|---|
Name |
string |
Badge text, for example Admin or VIP. |
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));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. |
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. |
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" }));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}");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");
}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. |
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.
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?.
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");
}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. |
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"));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")]);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.
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.
public sealed class LogsParserRegistrationOptionsThis 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 };
});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()}");
}
}
}
}- LogsParserClient — the methods that return these models
-
Requests and URI Builder —
LogsQuery,AdminActivityQuery,TopOperationsQuery - Parsers — building the same models from HTML without HTTP
-
Cookie Storage —
ParserCookieandICookieStoragein practice -
Transport and Authentication —
ParserRequestandLogsParserHttpOptionsat work -
Dependency Injection —
LogsParserRegistrationOptionsin a container -
Exceptions —
HtmlParsingExceptionand the rest of the hierarchy
LogsParser · CC BY-NC 4.0 — non-commercial use only / только некоммерческое использование
English
- Home
- Getting Started
- LogsParserClient
- Requests and URI Builder
- Models
- Parsers
- Transport and Authentication
- Cookie Storage
- Dependency Injection
- Logging
- Exceptions
Русский
- Главная
- Начало работы
- LogsParserClient
- Запросы и построитель URI
- Модели
- Парсеры
- Транспорт и авторизация
- Хранение cookies
- Внедрение зависимостей
- Логирование
- Исключения