Skip to content

Add a web label#883

Open
11EJDE11 wants to merge 6 commits intoCnCNet:developfrom
11EJDE11:feat-web-label-2
Open

Add a web label#883
11EJDE11 wants to merge 6 commits intoCnCNet:developfrom
11EJDE11:feat-web-label-2

Conversation

@11EJDE11
Copy link
Member

This PR adds a label that can query JSON data from a datasource. We can use this for the online player count, for ladder ranking, or news. INI datasources are also supported.

Datasources are specified in ClientDefinitions.ini

[DataSources]
DataSourceID=URL,RefreshIntervalSeconds,TimeoutSeconds,Format ;example: TopPlayers=https://ladder.cncnet.org/api/v1/qm/ladder/rankings,300,10,json|ini

Labels then point to a datasource and can query the JSON.

[ExtraControls]
1=lblTopPlayer1:XNAClientJSONLabel

[lblTopPlayer1]
$Type=XNAClientJSONLabel
DataSourceID=TopPlayers
Template=1. {YR[0].player_name} - {YR[0].points:N0} pts
LoadingText=Loading...
FallbackText=---
FontIndex=1
RemapColor=230,230,230
Location=360,166

See INISytem.md for more info.

Excuse the crappy screenshot.
image

@github-actions
Copy link

github-actions bot commented Nov 17, 2025

Nightly build for this pull request:

  • artifacts.zip
    This comment is automatic and is meant to allow guests to get latest automatic builds without registering. It is updated on every successful build.

Copy link
Member

@Metadorius Metadorius left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! Gave you some feedback.

Generally though while this approach definitely has it's place, I am not sure if we should do an explicit separate data source manager? My thoughts were more about something like an implicit manager class; different labels could have content URL endpoints specified right in place, and the response will be shared between them by that class (say, if the requests came to the manager in span of half a minute, then give the same data to every subscriber; if something updates the URL -- then all of the consumers of that URL will receive that).

Not fully set on the above, what are your thoughts?

cc @SadPencil @MahBoiDeveloper @Starkku @Rampastring

Comment on lines +158 to +179
private List<string> ParseJSONPath(string json, string path)
{
try
{
var root = Newtonsoft.Json.Linq.JToken.Parse(json);
var results = new List<string>();

var tokens = root.SelectTokens(path);

if (tokens != null)
foreach (var token in tokens)
if (token != null)
results.Add(token.ToString());

return results.Count > 0 ? results : null;
}
catch (Exception ex)
{
Logger.Log($"JSONPath error: {ex.Message}");
return null;
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should happen way earlier at manager stage, and the control should only receive a parsed json and do template substitution, otherwise we're duplicating work.

Also we already use System.Text.Json, I see that it has no support for JSON Path for some reason, maybe this extension to System.Text.Json would do better? https://www.nuget.org/packages/JsonPath.Net Just so we don't introduce two similar libraries.

public string Template { get; set; }
public string LoadingText { get; set; }
public int MaxResults { get; set; } = 0;
public string FallbackText { get; set; } = "N/A";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing .L10N call

private string ConvertIniToJson(string iniContent)
{
var iniFile = new IniFile();
using (var reader = new StringReader(iniContent))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a constructor in IniFile that takes a stream, no need to parse it by hand, just take a string and make a stream out of it 😄

Comment on lines +238 to +240
var sb = new StringBuilder();
sb.Append("{");
bool firstSection = true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The aforementioned bringing of JSON parsing earlier than that will allow you to construct the programmatic representation via a few foreaches, iterating sections and then keys to set values and greatly simplifying the code 🙂

if (string.IsNullOrEmpty(value))
continue;

// format: URL,RefreshIntervalSeconds,TimeoutSeconds,Format
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

URL could have commas in it, I think instead should do something like

[DataSourceName]
URL=https://...
RefreshIntervalSeconds=

Also I think individual data source parsing should be in the DataSource itself?

Comment on lines +19 to +20
private static JSONDataSourceManager _instance;
public static JSONDataSourceManager Instance => _instance ??= new JSONDataSourceManager();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have a DI container in use, I think you could use that? The codebase haven't fully migrated though, so tell me if you encounter any issues with that

private readonly string _url;
private readonly int _refreshIntervalSeconds;
private readonly int _timeoutSeconds;
private readonly string _format;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this _format should be either an enum and/or maybe even a tiny simple interface/class with one method that converts input value (stream?) and gives JSON for arbitrary data representation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with an interface. So we can decouple some string parsing logic from the UI logic. My recent editing experience on LAN lobby shows that it might waste a huge amount of time to maintain codes if different kinds of logics are not decoupled

{
private readonly string _id;
private readonly string _url;
private readonly int _refreshIntervalSeconds;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does it run even in background or when controls using it is not active?

although I realise now that it will probably complicate the code a lot, so I don't think there's a big need in doing it exactly. but if there is a lot of players that don't go online -- maybe autorefresh isn't something we may want to do? it will strain our servers quite a bit, maybe we should update just when the control is shown for the first time? (or sth in this direction)


namespace ClientGUI;

public class XNAClientJSONLabel : XNALabel
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps let's name it XNAClientWebLabel, because it's not just JSON anymore?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also maybe it could be good to have it as a descendant of XNALinkLabel? in case the users want to see details

public string Template { get; set; }
public string LoadingText { get; set; }
public int MaxResults { get; set; } = 0;
public string FallbackText { get; set; } = "N/A";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"N/A" is short for not applicable, instead of "not available". So I don't think we should not use "N/A" here

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's used interchangeably as either of those, so I think it's fine?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants