Skip to content

Commit 7f11055

Browse files
committed
feat: add alternative domains for La Liga censorship in Spain
1 parent 6610169 commit 7f11055

10 files changed

Lines changed: 383 additions & 8 deletions

Authentication/AuthenticationService.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ public AuthenticationService(
3434
_configService = configService;
3535

3636
MainMenuUi_Awake.Postfixed += OnMainMenuAwake;
37-
_configService.UseLocalDevelopmentBackend.SettingChanged += OnUseLocalDevelopmentBackendChanged;
37+
_configService.UseLocalDevelopmentBackend.SettingChanged += OnBackendSelectionChanged;
38+
_configService.UseAlternativeDomainsInSpain.SettingChanged += OnBackendSelectionChanged;
3839
}
3940

4041
private void OnMainMenuAwake()
@@ -50,7 +51,7 @@ private void OnMainMenuAwake()
5051
Login(false).Forget();
5152
}
5253

53-
private void OnUseLocalDevelopmentBackendChanged(object sender, EventArgs e)
54+
private void OnBackendSelectionChanged(object sender, EventArgs e)
5455
{
5556
Login(true).Forget();
5657
}

Configuration/ConfigService.cs

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,13 @@ namespace TNRD.Zeepkist.GTR.Configuration;
88
public class ConfigService : IEagerService
99
{
1010
public const string ProductionBackendUrl = "https://backend.zeepki.st";
11+
public const string AlternativeSpainBackendUrl =
12+
"https://es-backend-5eu0pg2dcfyp6u2v33lz.zeepki.st";
1113
public const string LocalDevelopmentBackendUrl = "http://127.0.0.1:3001";
1214
public const string CdnUrl = "https://cdn.zeepki.st";
1315
public const string ProductionGraphQLUrl = "https://graphql.zeepki.st";
16+
public const string AlternativeSpainGraphQLUrl =
17+
"https://es-graphql-1q5xcqmja6dvh4ctc4t9.zeepki.st/";
1418
public const string LocalDevelopmentGraphQLUrl = "http://127.0.0.1:5000/";
1519

1620
public ConfigEntry<bool> SubmitRecords { get; private set; }
@@ -53,16 +57,23 @@ public class ConfigService : IEagerService
5357
public ConfigEntry<bool> ButtonLinkDiscord { get; private set; }
5458
public ConfigEntry<bool> ButtonUnlinkDiscord { get; private set; }
5559

60+
public ConfigEntry<bool> UseAlternativeDomainsInSpain { get; private set; }
5661
public ConfigEntry<bool> UseLocalDevelopmentBackend { get; private set; }
5762
public ConfigEntry<bool> UseLocalDevelopmentGraphQL { get; private set; }
5863

59-
public string SelectedBackendUrl => UseLocalDevelopmentBackend.Value
60-
? LocalDevelopmentBackendUrl
61-
: ProductionBackendUrl;
64+
public string SelectedBackendUrl => ServiceUrlSelector.Select(
65+
UseLocalDevelopmentBackend.Value,
66+
UseAlternativeDomainsInSpain.Value,
67+
ProductionBackendUrl,
68+
AlternativeSpainBackendUrl,
69+
LocalDevelopmentBackendUrl);
6270

63-
public string SelectedGraphQLUrl => UseLocalDevelopmentGraphQL.Value
64-
? LocalDevelopmentGraphQLUrl
65-
: ProductionGraphQLUrl;
71+
public string SelectedGraphQLUrl => ServiceUrlSelector.Select(
72+
UseLocalDevelopmentGraphQL.Value,
73+
UseAlternativeDomainsInSpain.Value,
74+
ProductionGraphQLUrl,
75+
AlternativeSpainGraphQLUrl,
76+
LocalDevelopmentGraphQLUrl);
6677

6778
public ConfigEntry<KeyCode>[] PlaybackScrubProgressKeys { get; private set; }
6879
public ConfigEntry<KeyCode> PlaybackSpeedIncreaseKey { get; private set; }
@@ -300,6 +311,14 @@ private void ConfigDiscord(ConfigFile config)
300311

301312
private void ConfigUrls(ConfigFile config)
302313
{
314+
UseAlternativeDomainsInSpain = config.Bind(
315+
"5. URLs",
316+
"(Spain) Use Alternative URLs",
317+
false,
318+
"Route ZeepCentraal traffic through alternative\n" +
319+
"domains that do not use Cloudflare to avoid\n" +
320+
"La Liga censorship in Spain.");
321+
303322
UseLocalDevelopmentBackend = config.Bind(
304323
"5. URLs",
305324
"Local Backend",
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
namespace TNRD.Zeepkist.GTR.Configuration;
2+
3+
internal static class ServiceUrlSelector
4+
{
5+
public static string Select(
6+
bool useLocalDevelopment,
7+
bool useAlternativeDomains,
8+
string productionUrl,
9+
string alternativeUrl,
10+
string localDevelopmentUrl)
11+
{
12+
if (useLocalDevelopment)
13+
return localDevelopmentUrl;
14+
15+
return useAlternativeDomains ? alternativeUrl : productionUrl;
16+
}
17+
}

Dialogs/LaLigaCensorshipDialog.cs

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
using System;
2+
using Imui.Controls;
3+
using Imui.Core;
4+
using Imui.Style;
5+
using UnityEngine;
6+
using ZeepSDK.Controls;
7+
using ZeepSDK.UI;
8+
using ZeepSDK.Utilities;
9+
10+
namespace TNRD.Zeepkist.GTR.Dialogs;
11+
12+
internal sealed class LaLigaCensorshipDialog : IZeepGUIDrawer, IDisposable
13+
{
14+
public const string MoreInformationUrl = "https://hayahora.futbol";
15+
16+
private readonly Action<bool> _onDecision;
17+
private readonly DisposableBag _inputOverride;
18+
private bool _active = true;
19+
private bool _useAlternativeDomains;
20+
21+
public LaLigaCensorshipDialog(bool useAlternativeDomains, Action<bool> onDecision)
22+
{
23+
_useAlternativeDomains = useAlternativeDomains;
24+
_onDecision = onDecision ?? throw new ArgumentNullException(nameof(onDecision));
25+
_inputOverride = ControlsApi.DisableAllInputExceptEventSystem(() => _active);
26+
}
27+
28+
public void OnZeepGUI(ImGui gui)
29+
{
30+
if (!_active)
31+
return;
32+
33+
uint id = gui.PushId("LaLigaCensorshipPopup");
34+
try
35+
{
36+
gui.BeginPopup();
37+
ImRect rect = ImWindow.GetInitialWindowRect(gui, new ImSize(920, 650));
38+
39+
gui.Canvas.PushClipRect(rect);
40+
gui.Canvas.PushRectMask(rect, 0);
41+
gui.Box(rect, gui.Style.Window.Box);
42+
gui.RegisterControl(id, rect);
43+
gui.RegisterGroup(id, rect);
44+
gui.Layout.Push(ImAxis.Vertical, rect.WithPadding(24));
45+
46+
gui.Text("Connection issues in Spain", new ImTextSettings(36, 0.5f));
47+
gui.AddSpacing(10);
48+
DrawParagraph(gui,
49+
"During La Liga matches, Spanish internet providers block Cloudflare IP addresses at La Liga's " +
50+
"request. This goes beyond the websites covered by the court orders and blocks unrelated services " +
51+
"using Cloudflare infrastructure, including ZeepCentraal.");
52+
gui.AddSpacing(10);
53+
DrawParagraph(gui,
54+
"If you're affected, GTR may be unable to connect to ZeepCentraal, submit records, download ghosts " +
55+
"or use other online features.");
56+
gui.AddSpacing(10);
57+
DrawParagraph(gui,
58+
"You can use alternative ZeepCentraal domains that don't go through Cloudflare to avoid these blocks.");
59+
gui.AddSpacing(14);
60+
61+
_useAlternativeDomains = gui.Checkbox(
62+
_useAlternativeDomains,
63+
"Use alternative domains in Spain");
64+
65+
gui.AddSpacing(14);
66+
DrawParagraph(gui,
67+
"Please only enable this if you're having connection problems due to ISP blocking. You can turn it " +
68+
"off or on at any time in the GTR mod settings under \"Other -> 5. URLs\".");
69+
gui.AddSpacing(10);
70+
DrawParagraph(gui,
71+
"For more information about La Liga's censorship and how it affects internet access in Spain, visit:");
72+
gui.AddSpacing(6);
73+
74+
if (gui.Button(MoreInformationUrl, new ImSize(320, 42)))
75+
Application.OpenURL(MoreInformationUrl);
76+
77+
const float buttonWidth = 220;
78+
const float buttonHeight = 52;
79+
gui.AddSpacing(Mathf.Max(12, gui.GetLayoutHeight() - buttonHeight));
80+
81+
using (gui.Horizontal(gui.GetLayoutWidth(), buttonHeight))
82+
{
83+
gui.AddSpacing(Mathf.Max(0, gui.GetLayoutWidth() - buttonWidth));
84+
if (gui.Button("Continue", new ImSize(buttonWidth, buttonHeight)))
85+
Decide();
86+
}
87+
88+
gui.Layout.Pop();
89+
gui.Canvas.PopClipRect();
90+
gui.Canvas.PopClipRect();
91+
gui.EndPopup();
92+
}
93+
finally
94+
{
95+
gui.PopId();
96+
}
97+
}
98+
99+
public void Dispose()
100+
{
101+
if (!_active)
102+
return;
103+
104+
_active = false;
105+
_inputOverride.Dispose();
106+
}
107+
108+
private static void DrawParagraph(ImGui gui, string text)
109+
{
110+
gui.Text(text, new ImTextSettings(18, 0f, 0f, true));
111+
}
112+
113+
private void Decide()
114+
{
115+
if (!_active)
116+
return;
117+
118+
_active = false;
119+
_inputOverride.Dispose();
120+
_onDecision(_useAlternativeDomains);
121+
}
122+
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
using System;
2+
using System.Net.Http;
3+
using Microsoft.Extensions.Logging;
4+
using TNRD.Zeepkist.GTR.Configuration;
5+
using TNRD.Zeepkist.GTR.Core;
6+
using TNRD.Zeepkist.GTR.Patching.Patches;
7+
using UnityEngine;
8+
using ZeepSDK.Controls;
9+
using ZeepSDK.External.Cysharp.Threading.Tasks;
10+
using ZeepSDK.UI;
11+
12+
namespace TNRD.Zeepkist.GTR.Dialogs;
13+
14+
internal sealed class LaLigaCensorshipDialogService : IEagerService, IDisposable
15+
{
16+
public const string CountryDetectionClientKey = "IP Country Detection";
17+
18+
private const string NoticeSeenKey = "TNRD.Zeepkist.GTR.HasSeenLaLigaCensorshipNotice1";
19+
20+
private readonly IHttpClientFactory _httpClientFactory;
21+
private readonly ConfigService _configService;
22+
private readonly ILogger<LaLigaCensorshipDialogService> _logger;
23+
private LaLigaCensorshipDialog _dialog;
24+
private bool _checking;
25+
private bool _disposed;
26+
27+
public LaLigaCensorshipDialogService(
28+
IHttpClientFactory httpClientFactory,
29+
ConfigService configService,
30+
ILogger<LaLigaCensorshipDialogService> logger)
31+
{
32+
_httpClientFactory = httpClientFactory;
33+
_configService = configService;
34+
_logger = logger;
35+
MainMenuUi_Awake.Postfixed += OnMainMenuAwake;
36+
}
37+
38+
public void Dispose()
39+
{
40+
if (_disposed)
41+
return;
42+
43+
_disposed = true;
44+
MainMenuUi_Awake.Postfixed -= OnMainMenuAwake;
45+
RemoveDialog();
46+
}
47+
48+
private void OnMainMenuAwake()
49+
{
50+
if (_disposed || _checking || _dialog != null || HasSeenNotice())
51+
return;
52+
53+
_checking = true;
54+
DetectAndShowAsync().Forget(exception =>
55+
_logger.LogError(exception, "Failed to show Spain connection dialog"));
56+
}
57+
58+
private async UniTask DetectAndShowAsync()
59+
{
60+
try
61+
{
62+
if (_disposed || HasSeenNotice() || !await IsUserInSpainAsync())
63+
return;
64+
65+
await UniTask.SwitchToMainThread();
66+
await UniTask.WaitUntil(() => _disposed || ControlsApi.MenuInputOverride.Value);
67+
68+
if (_disposed || HasSeenNotice())
69+
return;
70+
71+
_dialog = new LaLigaCensorshipDialog(
72+
_configService.UseAlternativeDomainsInSpain.Value,
73+
ApplyDecision);
74+
UIApi.AddZeepGUIDrawer(_dialog);
75+
}
76+
finally
77+
{
78+
_checking = false;
79+
}
80+
}
81+
82+
private async UniTask<bool> IsUserInSpainAsync()
83+
{
84+
try
85+
{
86+
HttpClient client = _httpClientFactory.CreateClient(CountryDetectionClientKey);
87+
using HttpResponseMessage response = await client.GetAsync("json");
88+
if (!response.IsSuccessStatusCode)
89+
{
90+
_logger.LogWarning(
91+
"IP country detection failed with status {StatusCode}",
92+
response.StatusCode);
93+
return false;
94+
}
95+
96+
string content = await response.Content.ReadAsStringAsync();
97+
return SpainCountryDetection.IsSpanishResponse(content);
98+
}
99+
catch (Exception exception)
100+
{
101+
_logger.LogWarning(exception, "IP country detection failed");
102+
return false;
103+
}
104+
}
105+
106+
private void ApplyDecision(bool useAlternativeDomains)
107+
{
108+
PlayerPrefs.SetInt(NoticeSeenKey, 1);
109+
PlayerPrefs.Save();
110+
_configService.UseAlternativeDomainsInSpain.Value = useAlternativeDomains;
111+
RemoveDialog();
112+
}
113+
114+
private void RemoveDialog()
115+
{
116+
if (_dialog == null)
117+
return;
118+
119+
UIApi.RemoveZeepGUIDrawer(_dialog);
120+
_dialog.Dispose();
121+
_dialog = null;
122+
}
123+
124+
private static bool HasSeenNotice() => PlayerPrefs.GetInt(NoticeSeenKey, 0) == 1;
125+
}

Dialogs/SpainCountryDetection.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using System;
2+
using Newtonsoft.Json;
3+
4+
namespace TNRD.Zeepkist.GTR.Dialogs;
5+
6+
internal static class SpainCountryDetection
7+
{
8+
public static bool IsSpanishResponse(string response)
9+
{
10+
if (string.IsNullOrWhiteSpace(response))
11+
return false;
12+
13+
try
14+
{
15+
IpCountryResponse country = JsonConvert.DeserializeObject<IpCountryResponse>(response);
16+
return string.Equals(country?.Country, "ES", StringComparison.OrdinalIgnoreCase);
17+
}
18+
catch (JsonException)
19+
{
20+
return false;
21+
}
22+
}
23+
24+
private sealed class IpCountryResponse
25+
{
26+
[JsonProperty("country")]
27+
public string Country { get; set; }
28+
}
29+
}

0 commit comments

Comments
 (0)