Skip to content

Commit ebfe2a0

Browse files
committed
chore(release): merge dev into main for v0.2.35
2 parents 174a816 + 7d95db8 commit ebfe2a0

20 files changed

Lines changed: 329 additions & 70 deletions

Const.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public static class Const
2222
/// Assembly / manifest version string.
2323
/// 程序集/清单版本字符串。
2424
/// </summary>
25-
public const string Version = "0.2.34";
25+
public const string Version = "0.2.35";
2626

2727
/// <summary>
2828
/// Root key for RitsuLib JSON settings under the mod’s user folder.

STS2-RitsuLib.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030

3131
<PropertyGroup Label="NuGet package">
3232
<IsPackable>true</IsPackable>
33-
<Version>0.2.34</Version>
33+
<Version>0.2.35</Version>
3434
<Authors>OLC</Authors>
3535
<Description>Shared framework library for Slay the Spire 2 mods.</Description>
3636
<PackageReadmeFile>README.md</PackageReadmeFile>

Settings/Localization/ModSettingsUi/eng.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,14 @@
4444
"ritsulib.telemetry.field.endpoint": "Endpoint",
4545
"ritsulib.telemetry.field.consent": "Consent",
4646
"ritsulib.telemetry.field.grantedRequests": "Granted requests",
47+
"ritsulib.telemetry.field.grantedSharedContributions": "Shared sources",
4748
"ritsulib.telemetry.field.queuedEvents": "Queued events",
4849
"ritsulib.telemetry.request.line": "- {0} ({1}): {2}",
4950
"ritsulib.telemetry.requests.empty": "This applicant has no data requests.",
51+
"ritsulib.telemetry.shared.heading": "Additional shared data sources:",
52+
"ritsulib.telemetry.shared.line": "- {0}/{1} ({2}): {3}",
53+
"ritsulib.telemetry.shared.granted": "Granted",
54+
"ritsulib.telemetry.shared.notGranted": "Not granted",
5055
"ritsulib.telemetry.consent.granted": "Granted",
5156
"ritsulib.telemetry.consent.denied": "Denied",
5257
"ritsulib.telemetry.consent.unknown": "Not decided",
@@ -63,6 +68,8 @@
6368
"ritsulib.telemetry.prompt.bodyIntro": "The following mods request permission to send telemetry to their own fixed endpoints. Startup facts have already been sampled locally and will only be sent after consent.",
6469
"ritsulib.telemetry.prompt.endpoint": "Endpoint: {0}",
6570
"ritsulib.telemetry.prompt.requestLine": "- {0}: {1}",
71+
"ritsulib.telemetry.prompt.shared.heading": "Additional shared data sources:",
72+
"ritsulib.telemetry.prompt.shared.line": "- {0}/{1} ({2})",
6673
"ritsulib.telemetry.prompt.footer": "You can manage each applicant later from RitsuLib > Telemetry.",
6774
"ritsulib.telemetry.request.basicUsage.description": "Session start, framework version, platform, and anonymous install id.",
6875
"ritsulib.telemetry.request.modInventory.description": "Loaded mod list for compatibility and adoption analysis.",

Settings/Localization/ModSettingsUi/zhs.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,14 @@
4444
"ritsulib.telemetry.field.endpoint": "端点",
4545
"ritsulib.telemetry.field.consent": "授权状态",
4646
"ritsulib.telemetry.field.grantedRequests": "已授权申请",
47+
"ritsulib.telemetry.field.grantedSharedContributions": "共享来源",
4748
"ritsulib.telemetry.field.queuedEvents": "排队事件",
4849
"ritsulib.telemetry.request.line": "- {0}({1}):{2}",
4950
"ritsulib.telemetry.requests.empty": "此申请方没有数据申请。",
51+
"ritsulib.telemetry.shared.heading": "额外共享数据来源:",
52+
"ritsulib.telemetry.shared.line": "- {0}/{1}({2}):{3}",
53+
"ritsulib.telemetry.shared.granted": "已授权",
54+
"ritsulib.telemetry.shared.notGranted": "未授权",
5055
"ritsulib.telemetry.consent.granted": "已授权",
5156
"ritsulib.telemetry.consent.denied": "已拒绝",
5257
"ritsulib.telemetry.consent.unknown": "未决定",
@@ -63,6 +68,8 @@
6368
"ritsulib.telemetry.prompt.bodyIntro": "以下模组申请将遥测数据发送到各自的固定端点。启动信息已先在本机采样,只有授权后才会发送。",
6469
"ritsulib.telemetry.prompt.endpoint": "端点:{0}",
6570
"ritsulib.telemetry.prompt.requestLine": "- {0}:{1}",
71+
"ritsulib.telemetry.prompt.shared.heading": "额外共享数据来源:",
72+
"ritsulib.telemetry.prompt.shared.line": "- {0}/{1}({2})",
6673
"ritsulib.telemetry.prompt.footer": "之后可在 RitsuLib > 遥测授权 中管理每个申请方。",
6774
"ritsulib.telemetry.request.basicUsage.description": "启动会话、框架版本、平台与匿名安装 ID。",
6875
"ritsulib.telemetry.request.modInventory.description": "已加载模组列表,用于兼容性和使用情况分析。",

Telemetry/Consent/TelemetryConsentStore.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ public static void SetApplicantConsent(
6565
consent.GrantedRequests = grantedRequests == null
6666
? []
6767
: new(grantedRequests, StringComparer.OrdinalIgnoreCase);
68+
if (state != TelemetryConsentState.Granted)
69+
consent.SharedContributionSources.Clear();
6870
Save();
6971
RitsuLibFramework.Logger.Info(
7072
$"[Telemetry] Applicant consent updated: {applicantId} -> {state} ({consent.GrantedRequests.Count} granted request(s)).");

Telemetry/Contributions/ITelemetryContributionProvider.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,10 @@ public interface ITelemetryContributionProvider
3333
TelemetryContributionVisibility Visibility { get; }
3434

3535
/// <summary>
36-
/// Builds the contribution payload for the current telemetry event.
37-
/// 为当前 telemetry 事件构建 contribution 数据。
36+
/// Builds the contribution payload for the current telemetry event. Private contributions are only attached
37+
/// to the owning applicant; shared contributions require explicit source consent.
38+
/// 为当前 telemetry 事件构建 contribution 数据。私有 contribution 仅附加到拥有者自己的申请;
39+
/// 共享 contribution 需要额外的来源授权。
3840
/// </summary>
3941
JsonNode? Build(TelemetryContributionContext context);
4042
}

Telemetry/Contributions/TelemetryContributionVisibility.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@ namespace STS2RitsuLib.Telemetry
1010
public enum TelemetryContributionVisibility
1111
{
1212
/// <summary>
13-
/// Contribution is private to the owning applicant and is never routed as shared context.
14-
/// contribution 仅属于拥有它的申请方,不会作为共享上下文路由。
13+
/// Contribution is private to the owning applicant and is attached to that applicant's own subscribed
14+
/// requests without extra shared-source consent.
15+
/// contribution 仅属于拥有它的申请方,会附加到该申请方自己订阅的申请中,
16+
/// 不需要额外的共享来源授权。
1517
/// </summary>
1618
PrivateToApplicant,
1719

Telemetry/Core/TelemetryEnvelope.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ public sealed class TelemetryEnvelope
5151
public Dictionary<string, object?> Properties { get; init; } = [];
5252

5353
/// <summary>
54-
/// Structured payload, usually split into base payload, shared contributions, and applicant payload.
55-
/// 结构化数据,通常分为基础数据、共享 contributions 和申请方数据。
54+
/// Structured payload, usually split into base payload, private/shared contributions, and applicant payload.
55+
/// 结构化数据,通常分为基础数据、私有/共享 contributions 和申请方数据。
5656
/// </summary>
5757
public JsonNode? Payload { get; init; }
5858
}

Telemetry/Core/TelemetryEnvelopeFactory.cs

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,23 @@ private static JsonObject BuildPayload(
6060
if (basePayload.Count > 0)
6161
root["base_payload"] = basePayload;
6262

63-
var shared = BuildSharedContributions(applicant, request, eventName, basePayload);
63+
var privateContributions = BuildContributions(
64+
TelemetryRegistry.ResolvePrivateContributions(applicant, request),
65+
applicant,
66+
request,
67+
eventName,
68+
basePayload,
69+
"Private");
70+
if (privateContributions.Count > 0)
71+
root["private_contributions"] = privateContributions;
72+
73+
var shared = BuildContributions(
74+
TelemetryRegistry.ResolveSharedContributions(applicant, request),
75+
applicant,
76+
request,
77+
eventName,
78+
basePayload,
79+
"Shared");
6480
if (shared.Count > 0)
6581
root["shared_contributions"] = shared;
6682

@@ -97,14 +113,15 @@ private static JsonObject BuildBasePayload(TelemetryRequest request)
97113
};
98114
}
99115

100-
private static JsonObject BuildSharedContributions(
116+
private static JsonObject BuildContributions(
117+
IReadOnlyList<ITelemetryContributionProvider> providers,
101118
TelemetryApplicant applicant,
102119
TelemetryRequest request,
103120
string eventName,
104-
JsonNode? basePayload)
121+
JsonNode? basePayload,
122+
string logPrefix)
105123
{
106124
var root = new JsonObject();
107-
var providers = TelemetryRegistry.ResolveSharedContributions(applicant, request);
108125
foreach (var provider in providers)
109126
{
110127
JsonNode? node;
@@ -121,7 +138,7 @@ private static JsonObject BuildSharedContributions(
121138
catch (Exception ex)
122139
{
123140
RitsuLibFramework.Logger.Warn(
124-
$"[Telemetry] Shared contribution '{provider.ContributorModId}/{provider.ContributionId}' failed: {ex.Message}");
141+
$"[Telemetry] {logPrefix} contribution '{provider.ContributorModId}/{provider.ContributionId}' failed: {ex.Message}");
125142
continue;
126143
}
127144

Telemetry/Core/TelemetryRegistry.cs

Lines changed: 81 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
namespace STS2RitsuLib.Telemetry
44
{
55
/// <summary>
6-
/// Process-wide registry for telemetry applicants and shared contribution providers.
7-
/// 进程级 telemetry 申请方和共享 contribution provider 注册表。
6+
/// Process-wide registry for telemetry applicants and contribution providers.
7+
/// 进程级 telemetry 申请方和 contribution provider 注册表。
88
/// </summary>
99
public static class TelemetryRegistry
1010
{
@@ -39,8 +39,8 @@ public static void RegisterApplicant(TelemetryApplicant applicant)
3939
}
4040

4141
/// <summary>
42-
/// Registers or replaces a shared contribution provider.
43-
/// 注册或替换一个共享 contribution provider。
42+
/// Registers or replaces a telemetry contribution provider.
43+
/// 注册或替换一个 telemetry contribution provider。
4444
/// </summary>
4545
public static void RegisterContributionProvider(ITelemetryContributionProvider provider)
4646
{
@@ -109,7 +109,7 @@ internal static IReadOnlyList<ITelemetryContributionProvider> ResolveSharedContr
109109
TelemetryApplicant applicant,
110110
TelemetryRequest request)
111111
{
112-
var subscriptions = request.SharedContributionSubscriptions;
112+
var subscriptions = request.ContributionSubscriptions;
113113
if (subscriptions.Count == 0)
114114
return [];
115115

@@ -119,8 +119,7 @@ internal static IReadOnlyList<ITelemetryContributionProvider> ResolveSharedContr
119119
.Where(provider =>
120120
provider.Visibility == TelemetryContributionVisibility.SharedToAuthorizedSubscribers)
121121
.Where(provider => provider.Category == request.Category)
122-
.Where(provider =>
123-
subscriptions.Contains(provider.ContributionId, StringComparer.OrdinalIgnoreCase))
122+
.Where(provider => SubscriptionMatches(provider, applicant, subscriptions, false))
124123
.Where(provider => TelemetryConsentStore.IsSharedContributionGranted(
125124
applicant.ApplicantId,
126125
provider.ContributorModId,
@@ -129,6 +128,81 @@ internal static IReadOnlyList<ITelemetryContributionProvider> ResolveSharedContr
129128
}
130129
}
131130

131+
internal static IReadOnlyList<ITelemetryContributionProvider> ResolvePrivateContributions(
132+
TelemetryApplicant applicant,
133+
TelemetryRequest request)
134+
{
135+
var subscriptions = request.ContributionSubscriptions;
136+
if (subscriptions.Count == 0)
137+
return [];
138+
139+
lock (Sync)
140+
{
141+
return ContributionProviders.Values
142+
.Where(provider => provider.Visibility == TelemetryContributionVisibility.PrivateToApplicant)
143+
.Where(provider => provider.Category == request.Category)
144+
.Where(provider => SubscriptionMatches(provider, applicant, subscriptions, true))
145+
.Where(provider => IsOwnedByApplicant(provider, applicant))
146+
.ToArray();
147+
}
148+
}
149+
150+
internal static IReadOnlyList<ITelemetryContributionProvider> GetRequestedSharedContributions(
151+
TelemetryApplicant applicant)
152+
{
153+
lock (Sync)
154+
{
155+
return ContributionProviders.Values
156+
.Where(provider =>
157+
provider.Visibility == TelemetryContributionVisibility.SharedToAuthorizedSubscribers)
158+
.Where(provider => applicant.Requests.Any(request =>
159+
request.Category == provider.Category &&
160+
SubscriptionMatches(provider, applicant, request.ContributionSubscriptions, false)))
161+
.OrderBy(provider => provider.ContributorModId, StringComparer.OrdinalIgnoreCase)
162+
.ThenBy(provider => provider.ContributionId, StringComparer.OrdinalIgnoreCase)
163+
.ToArray();
164+
}
165+
}
166+
167+
private static bool IsOwnedByApplicant(
168+
ITelemetryContributionProvider provider,
169+
TelemetryApplicant applicant)
170+
{
171+
return string.Equals(provider.ContributorModId, applicant.OwnerModId, StringComparison.OrdinalIgnoreCase) ||
172+
string.Equals(provider.ContributorModId, applicant.ApplicantId, StringComparison.OrdinalIgnoreCase);
173+
}
174+
175+
private static bool SubscriptionMatches(
176+
ITelemetryContributionProvider provider,
177+
TelemetryApplicant applicant,
178+
IReadOnlyList<string> subscriptions,
179+
bool allowUnqualifiedOwnedContribution)
180+
{
181+
foreach (var subscription in subscriptions)
182+
{
183+
var value = subscription.Trim();
184+
if (string.IsNullOrEmpty(value))
185+
continue;
186+
187+
if (allowUnqualifiedOwnedContribution &&
188+
IsOwnedByApplicant(provider, applicant) &&
189+
string.Equals(value, provider.ContributionId, StringComparison.OrdinalIgnoreCase))
190+
return true;
191+
192+
if (string.Equals(
193+
value,
194+
$"{provider.ContributorModId}/{provider.ContributionId}",
195+
StringComparison.OrdinalIgnoreCase) ||
196+
string.Equals(
197+
value,
198+
$"{provider.ContributorModId}:{provider.ContributionId}",
199+
StringComparison.OrdinalIgnoreCase))
200+
return true;
201+
}
202+
203+
return false;
204+
}
205+
132206
private static string BuildContributionKey(string contributorModId, string contributionId)
133207
{
134208
return $"{contributorModId}\n{contributionId}";

0 commit comments

Comments
 (0)