CodeLogic.SocialConnect
5.0.121
dotnet add package CodeLogic.SocialConnect --version 5.0.121
NuGet\Install-Package CodeLogic.SocialConnect -Version 5.0.121
<PackageReference Include="CodeLogic.SocialConnect" Version="5.0.121" />
<PackageVersion Include="CodeLogic.SocialConnect" Version="5.0.121" />
<PackageReference Include="CodeLogic.SocialConnect" />
paket add CodeLogic.SocialConnect --version 5.0.121
#r "nuget: CodeLogic.SocialConnect, 5.0.121"
#:package CodeLogic.SocialConnect@5.0.121
#addin nuget:?package=CodeLogic.SocialConnect&version=5.0.121
#tool nuget:?package=CodeLogic.SocialConnect&version=5.0.121
CodeLogic.SocialConnect
Discord webhooks and Steam integration for CodeLogic 5 — send rich Discord notifications, read Steam player profiles, bans, and game libraries, and add "Sign in through Steam" to a website.
Four services behind one library: a Discord webhook sender (text, rich embeds, full payloads), a Steam profile reader (players, bans, owned games — cached), Steam authentication for validating game-session tickets, and Steam sign-in (OpenID 2.0) for websites. No external NuGet dependencies — built on System.Net.Http.Json.
Install
dotnet add package CodeLogic.SocialConnect
Quick start
using CL.SocialConnect;
using CL.SocialConnect.Models; // SteamPlayer, SocialConnectErrorCodes, ...
using CodeLogic.Core.Results;
await Libraries.LoadAsync<SocialConnectLibrary>();
await CodeLogic.ConfigureAsync();
await CodeLogic.StartAsync();
var social = Libraries.Get<SocialConnectLibrary>();
// Discord — send a webhook text message (Result, no value)
Result sent = await social.Discord.SendMessageAsync("Server restarted");
if (sent.IsFailure)
Console.WriteLine(sent.Error?.Message);
// Steam — fetch a player profile (Result<SteamPlayer>)
if (social.HasSteam)
{
Result<SteamPlayer> player = await social.Steam.GetPlayerAsync("76561198012345678");
Console.WriteLine(player switch
{
SteamPlayer p => $"{p.PersonaName} — {p.ProfileUrl}",
NotFoundError => "No such player", // social.steam.player_not_found
TimeoutError => "Steam did not answer in time", // social.steam.timeout
Error e => $"Steam lookup failed: {e.Code}", // a SocialConnectErrorCodes constant
null => "No result",
});
}
// Steam — "Sign in through Steam" on a website (OpenID 2.0; returns the Steam ID)
if (social.HasSteamOpenId)
{
// 1. Start: a random per-attempt state goes into return_to AND into an HttpOnly cookie, then redirect.
var state = SteamOpenIdService.CreateState();
response.Cookies.Append("steam_state", state, new CookieOptions { HttpOnly = true, Secure = true, SameSite = SameSiteMode.Lax });
string loginUrl = social.OpenId.BuildLoginUrl($"https://example.com/auth/steam/callback?state={state}");
// 2. Callback: read the state back from the cookie, rebuild the exact return_to, verify.
// A missing cookie, or one that differs from the ?state= Steam echoes, means this browser never started the login.
var expectedState = request.Cookies["steam_state"];
if (expectedState is null || expectedState != request.Query["state"]) return Unauthorized();
var returnTo = $"https://example.com/auth/steam/callback?state={expectedState}";
Result<SteamOpenIdResult> signIn = await social.OpenId.VerifyAsync(request.QueryString.Value, returnTo);
response.Cookies.Delete("steam_state");
if (signIn is SteamOpenIdResult user)
Console.WriteLine($"Signed in as {user.SteamId}"); // a replayed assertion fails with social.steam.openid_replayed
}
Set Steam.OpenIdRealm (for example https://example.com/) so a return_to built from user input cannot send the browser elsewhere.
Accessing a disabled service throws InvalidOperationException. Guard with HasDiscord / HasSteam / HasSteamAuth / HasSteamOpenId when a service may be turned off — Steam is disabled by default.
Features
- Discord webhooks —
SendMessageAsync(plain text, max 2000 chars),SendEmbedAsync(up to 10 rich embeds), andSendAsync(fullDiscordWebhookMessage); all returnResult. Onlyhttps://discord.com/api/webhooks/{id}/{token}URLs (anddiscordapp.com,ptb./canary.) are accepted (IsValidWebhookUrl); logs and events carry the URL with the token redacted. - Rich embeds — title, description, colour, fields, author, footer, image, thumbnail, and timestamp via
DiscordEmbed/DiscordEmbedField. - Mention control —
DiscordAllowedMentions(with.None/.All) whitelists which roles/users/@everyonemay ping; opt-in TTS viaDiscordWebhookMessage.Tts. - Steam profiles —
GetPlayerAsync,GetPlayerBansAsync,GetOwnedGamesAsyncreturnResult<T>with computed helpers (IsPublic,IsInGame,HasAnyBan,TotalPlaytime,LastPlayed). Steam IDs must be SteamID64s (SteamIds.IsSteamId64), otherwisesocial.steam.invalid_steamid. - Built-in cache — Steam reads are cached for
CacheTtlSeconds(default 300), at mostMaxCacheEntries(default 10 000) per cache with the oldest evicted when full; clear on demand withSteam.ClearCache(). - Steam authentication —
Auth.AuthenticateAsyncvalidates a session ticket (per-callappIdoverride) and returnsResult<SteamTicketIdentity>; a rejected ticket is anUnauthorizedError. - Steam sign-in —
OpenId.BuildLoginUrl/OpenId.VerifyAsyncimplement "Sign in through Steam" (OpenID 2.0): local checks of the signed assertion (endpoint,return_to, identity, signed fields,response_nonceage), a replay check against anIOpenIdNonceStore(social.steam.openid_replayed), then confirmation by Steam (check_authentication).OpenIdRealmpinsreturn_toto your site;CreateState()gives the per-attempt state. - Cancellation — every network call takes a
CancellationToken(last parameter); a cancelled call is anUnavailableError(social.steam.cancelled/social.discord.cancelled), never an exception. - Stable error codes — every failure is a CodeLogic
ErrorwhoseCodeis aSocialConnectErrorCodesconstant (social.discord.*,social.steam.*); switch on the code or theErrorkind, not on the message. - Testable — every service has a constructor taking an
HttpMessageHandler(fakes, outbound proxies). - Events —
WebhookSentEvent,SteamProfileFetchedEvent,SteamAuthenticatedEvent, andSteamOpenIdVerifiedEventon the CodeLogic event bus.
Configuration
Auto-generated on first run as config.socialconnect.json:
{
"enabled": true,
"discord": {
"enabled": true,
"defaultWebhookUrl": "https://discord.com/api/webhooks/xxx/yyy",
"timeoutSeconds": 10,
"defaultUsername": "MyApp",
"defaultAvatarUrl": "https://example.com/bot.png"
},
"steam": {
"enabled": false,
"apiKey": "your-steam-web-api-key",
"authEnabled": false,
"appId": "",
"openIdEnabled": true,
"openIdNonceMaxAgeSeconds": 300,
"openIdRealm": "https://example.com/",
"cacheTtlSeconds": 300,
"maxCacheEntries": 10000,
"timeoutSeconds": 15,
"apiBaseUrl": "https://api.steampowered.com"
}
}
| Setting | Default | Description |
|---|---|---|
Enabled |
true |
Master switch for the whole library. |
Discord.Enabled |
true |
Enable the Discord webhook service. |
Discord.DefaultWebhookUrl |
"" |
Webhook used when a call omits webhookUrl (secret); must be a Discord webhook URL. |
Discord.TimeoutSeconds |
10 |
HTTP timeout for Discord calls (1–120). |
Discord.DefaultUsername |
"" |
Default sender name for SendMessageAsync / SendEmbedAsync. |
Discord.DefaultAvatarUrl |
"" |
Default sender avatar for SendMessageAsync / SendEmbedAsync. |
Steam.Enabled |
false |
Enable the Steam services. The profile service requires ApiKey; sign-in (OpenID) alone does not. |
Steam.ApiKey |
"" |
Steam Web API key (secret) — get one at steamcommunity.com/dev/apikey. |
Steam.AuthEnabled |
false |
Enable ticket authentication; requires AppId and ApiKey. |
Steam.AppId |
"" |
Your game's Steam App ID (used by auth). |
Steam.OpenIdEnabled |
true |
Enable "Sign in through Steam" (OpenID) while Steam is enabled. OpenID itself needs no key. |
Steam.OpenIdNonceMaxAgeSeconds |
300 |
Oldest sign-in assertion accepted, by its response_nonce timestamp (social.steam.openid_expired); 0 = no age limit. |
Steam.OpenIdRealm |
"" |
Site every sign-in return_to must lie inside (absolute http(s) URL). Empty = derived from the return URL; set it unless return_to is a constant. |
Steam.CacheTtlSeconds |
300 |
Profile cache lifetime in seconds (must be > 0). |
Steam.MaxCacheEntries |
10000 |
Upper bound per profile cache (players, bans, games); the oldest entries are evicted when full. |
Steam.TimeoutSeconds |
15 |
HTTP timeout for Steam calls (1–120). |
Steam.ApiBaseUrl |
https://api.steampowered.com |
Steam Web API base URL; must be https without user info (the API key is sent to it). |
Changing config while running
config.socialconnect.json applies live when changed through Libraries.UpdateConfigAsync, Libraries.ReloadConfigAsync, or the config file watcher. Only the service whose settings changed is rebuilt:
- Discord settings:
social.Discordis replaced. - Steam settings:
social.Steam,social.Authandsocial.OpenIdare replaced; the profile cache starts empty. - Enabled flags: each service, and the whole library, can be switched on or off.
A replaced service is disposed only after its request timeout, so requests already running on it finish. Read social.Discord / social.Steam / social.Auth / social.OpenId when you use them rather than keeping an old instance. An invalid configuration is logged and ignored; the last valid one stays active.
Documentation
Full guide: CL.SocialConnect documentation
Requirements
- CodeLogic 5.0+ · .NET 11
- No external NuGet dependencies
License
MIT — see LICENSE.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net11.0 is compatible. |
-
net11.0
- CodeLogic (>= 5.0.37 && < 6.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 5.0.121 | 42 | 11/10/2026 |
| 5.0.118-preview | 34 | 11/8/2026 |
| 5.0.117-preview | 38 | 11/7/2026 |
| 5.0.116-preview | 41 | 11/1/2026 |
| 5.0.115-preview | 92 | 11/1/2026 |
| 5.0.114-preview | 39 | 10/31/2026 |
| 5.0.113-preview | 39 | 10/31/2026 |
| 4.9.106 | 52 | 10/30/2026 |
| 4.9.100 | 51 | 10/26/2026 |
| 4.9.98 | 54 | 10/25/2026 |
| 4.8.93 | 73 | 9/23/2026 |
| 4.8.87 | 81 | 9/13/2026 |
| 4.8.85 | 65 | 9/12/2026 |
| 4.6.72 | 234 | 6/20/2026 |
| 4.6.69-preview | 63 | 6/20/2026 |
| 4.5.2 | 142 | 5/24/2026 |
| 4.5.2-preview.68 | 94 | 6/20/2026 |
| 4.5.1 | 196 | 5/24/2026 |
| 4.5.1-preview.56 | 125 | 5/24/2026 |
| 4.4.1 | 141 | 5/24/2026 |
# CL.SocialConnect — Changelog
All notable changes to **CodeLogic.SocialConnect** are documented here. Versions follow
[Semantic Versioning](https://semver.org/).
## 5.0 (2026-11-10)
### Changed
- Record only provider-verified nonces; cap verification concurrency and replay-store capacity, refusing overload without evicting valid nonces. Failed assertions consume no replay entries. Use the shared bounded log formatter.
### Breaking
- Targets **.NET 11** (`net11.0`) and requires **CodeLogic 5.0** (`[5.0.0, 6.0.0)`). On .NET 10, stay on
4.9.x. CodeLogic 5 makes `Result<T>` / `Result` C# 15 unions and `Error` a closed record with
kind subtypes (`NotFoundError`, `ValidationError`, ...). Results can now be matched
exhaustively with `switch`.
- **The `SocialError` enum is removed; error codes are `SocialConnectErrorCodes` constants.** The codes keep their
values (`social.discord.*`, `social.steam.*`), and messages no longer start with the enum name in brackets.
| Old | New |
|---|---|
| `SocialError` enum | `SocialConnectErrorCodes` string constants, matched against `Error.Code` |
| `"[WebhookDeliveryFailed] HTTP 400"` | `"Webhook delivery failed: HTTP 400"` (`social.discord.delivery_failed`) |
| `"[Timeout] ..."`, `"[NetworkError] ..."`, `"[AuthenticationFailed] ..."`, `"[UserNotFound] ..."`, `"[SerializationError] ..."`, `"[ConfigurationError] ..."` | the same message without the bracketed prefix; also in the failure text of `WebhookSentEvent`, `SteamAuthenticatedEvent` and `SteamOpenIdVerifiedEvent` |
| `"social.discord.no_url"`, `"social.steam.timeout"`, ... (string literals) | `SocialConnectErrorCodes.DiscordNoUrl`, `SocialConnectErrorCodes.SteamTimeout`, ... (same values) |
Code that parsed the `[Name]` prefix out of `Error.Message` must switch on `Error.Code` (or the `Error` kind).
- `Auth.AuthenticateAsync` returns `Result<SteamTicketIdentity>` instead of `Result<SteamAuthResult>`.
The mutable `SteamAuthResult` class (with `IsAuthenticated`, `ErrorDescription` and `0`/`1` ints for
bans) is gone: the immutable `SteamTicketIdentity(SteamId, OwnerSteamId, AppId, VacBanned, PublisherBanned)`
record (plus `IsFamilyShared`) is only ever a success, and every failure is an `Error` kind —
`ValidationError` `social.steam.invalid_ticket` / `social.steam.no_appid`, `UnauthorizedError`
`social.steam.auth_failed`, `TimeoutError` `social.steam.timeout`, `UnavailableError`
`social.steam.network_error`, `InternalError` `social.steam.request_rejected` / `parse_error` / `error`.
- A ticket that is not hex is now refused locally with `social.steam.invalid_ticket` (it used to be
pasted unescaped into the request URL). A 4xx from Steam (e.g. a bad API key) is now
`InternalError` `social.steam.request_rejected` instead of `UnavailableError` `social.steam.network_error`.
- **Steam OpenID checks `openid.response_nonce`.** `VerifyAsync` never looked at the nonce, so an intercepted
assertion stayed usable indefinitely. It now refuses, before asking Steam, a nonce without a valid
`yyyy-MM-ddTHH:mm:ssZ` prefix or dated more than a minute ahead (`social.steam.openid_invalid`) and an
assertion older than the new `SteamConfig.OpenIdNonceMaxAgeSeconds` (default 300; `0` turns the age check
off) with `UnauthorizedError` `social.steam.openid_expired`. `SteamOpenIdResult` gains the required
`ResponseNonce` and `NonceIssuedAtUtc`: record accepted nonces and refuse a repeat to stop replays inside
the window. Code that constructs `SteamOpenIdResult` itself must set both.
- **Discord webhook URLs are restricted to Discord (SSRF).** `SendMessageAsync` / `SendEmbedAsync` / `SendAsync`
posted to any URL they were given. Only `https` URLs on `discord.com` / `discordapp.com` (and their `ptb.` /
`canary.` subdomains) with a `/api[/v{n}]/webhooks/{id}/{token}` path are accepted now; anything else fails
with `ValidationError` `social.discord.invalid_url` before a request, and redirects are not followed. A
`Discord.DefaultWebhookUrl` that is not such a URL fails configuration validation.
- **`WebhookSentEvent.WebhookUrl` no longer carries the webhook token.** It holds the redacted URL
(`https://discord.com/api/webhooks/{id}/***`); the new `WebhookId` property holds the id.
- **Steam profile reads validate the Steam ID.** `GetPlayerAsync`, `GetPlayerBansAsync` and `GetOwnedGamesAsync`
spliced `steamId` and the API key unescaped into the query string, so `"7656…&key=…"` could add or override
parameters. A Steam ID that is not a SteamID64 (17 digits, `7656119…`) now fails with `ValidationError`
`social.steam.invalid_steamid` before a request, and both values are URL-escaped.
- **`Steam.ApiBaseUrl` must be an absolute `https` URL without user info, query or fragment** (the API key travels
in every request's query string, so `http` would send it in clear). `SocialConnectConfig.Validate` refuses
anything else, and `SteamProfileService` / `SteamAuthenticationService` throw `ArgumentException` for such a
config instead of building a client for it. `SteamConfig.TryGetApiBaseUri` is the check.
- **Steam sign-in refuses a replayed assertion.** `SteamOpenIdService.VerifyAsync` records each verified
`openid.response_nonce` in an `IOpenIdNonceStore` (default: in memory, per process) and refuses one it has seen
with `UnauthorizedError` `social.steam.openid_replayed`. The nonce is recorded only after Steam confirms the
assertion, so a failed confirmation consumes no entry. Applications no longer need their own
nonce tracking, unless they run several servers without a shared store.
- **Cancellation is an error, not an exception.** `SteamOpenIdService.VerifyAsync` rethrew the caller's
`OperationCanceledException`; it now returns `UnavailableError` `social.steam.cancelled`. The new
`CancellationToken` parameters on the other services map the same way (`social.steam.cancelled`,
`social.discord.cancelled`), following the CL.Storage convention.
- **`Auth.AuthenticateAsync` checks Steam's answer.** A `steamid` or `ownersteamid` that is not a SteamID64 fails
with `InternalError` `social.steam.parse_error`; no identity is published or returned.
- `SteamProfileService` throws `ArgumentNullException` for a `null` config (it used to throw a
`NullReferenceException` later).
### Added
- `DiscordWebhookService.IsValidWebhookUrl(url)` and `DiscordWebhookService.RedactWebhookUrl(url)`.
- `SteamIds.IsSteamId64(value)` (`CL.SocialConnect.Models.Steam`).
- `WebhookSentEvent.WebhookId`.
- `SteamAuthenticationService(SteamConfig, HttpMessageHandler, ILogger?, IEventBus?)` constructor for
tests and outbound proxies, matching `SteamOpenIdService`.
- `DiscordWebhookService(DiscordConfig, HttpMessageHandler, …)` and `SteamProfileService(SteamConfig, HttpMessageHandler, …)`
constructors, so every service can be tested or routed through a proxy. A `SocketsHttpHandler` or
`HttpClientHandler` given to the Discord service has automatic redirects switched off.
- **`Steam.OpenIdRealm`** (default empty): the site Steam sign-in belongs to. When set, every `return_to` passed to
`BuildLoginUrl` must lie inside it (same scheme, host and port, path under the realm's path) or the call throws
`ArgumentException`, and it is the realm Steam shows unless the call gives one. Without it, and without a
`realm` argument, the realm is still derived from `return_to` itself, which accepts any absolute URL: set
`OpenIdRealm` whenever `return_to` is not a constant in your code.
- `SteamOpenIdService.CreateState()`: 256 random bits as a URL-safe base64url string, for the per-attempt state in
`return_to` and the cookie.
- `IOpenIdNonceStore` / `InMemoryOpenIdNonceStore`, and a `nonceStore` parameter on both `SteamOpenIdService`
constructors. The library shares one in-memory store across live config changes.
- **`Steam.MaxCacheEntries`** (default 10 000): each profile cache (players, bans, games) is bounded; expired entries
are dropped when read and the oldest entries are evicted when a cache is full. They grew without limit before.
- `CancellationToken` (last parameter, default `default`) on `SteamProfileService.GetPlayerAsync` /
`GetPlayerBansAsync` / `GetOwnedGamesAsync`, `SteamAuthenticationService.AuthenticateAsync` and
`DiscordWebhookService.SendMessageAsync` / `SendEmbedAsync` / `SendAsync`.
### Fixed
- **A live config change cannot wipe the Steam services.** `OnConfigChangedAsync` cleared `Steam`, `Auth` and
`OpenId` before constructing their replacements, so a constructor failure left all three gone. The new services
are built first and swapped in afterwards, and a configuration the services refuse is logged and ignored.
- **A hostile OpenID parameter name no longer reaches logs and events verbatim.** The name of a repeated
`openid.*` parameter was echoed into the log line, the `SteamOpenIdVerifiedEvent` and the error message after
URL-decoding, so `%0D%0A` could forge log lines. Control characters are now replaced and the name is cut to 64
characters.
- `DiscordWebhookService.SendAsync` disposes the `HttpResponseMessage`.
- **Steam sign-in works without a Steam Web API key**, as documented. Validation demanded `ApiKey` whenever
Steam was enabled, so OpenID could not be used on its own. With `Steam.Enabled` and `OpenIdEnabled` on and no
key, the config is valid and only `OpenId` is created (`HasSteam` is `false`). Ticket authentication
(`AuthEnabled`) now requires the key explicitly.
- **Live config changes are validated by the library too.** An invalid configuration that reaches
`OnConfigChangedAsync` is logged and ignored, and the running services keep the last valid one; later changes
are compared with the configuration actually running.
- `DiscordUser.GetAvatarUrl` no longer throws on a non-numeric discriminator, and escapes the id and avatar hash.
### Documentation
- The model reference matches the code: the Discord and Steam models are (non-sealed) classes with `List<>`
collections, `DiscordEmbed.Timestamp` is a `DateTime?`, and the embed sub-models use `required` members.
The examples use collection expressions and compile against the library.
- `DiscordUser` and `DiscordOAuthToken` are documented as data models for applications that run Discord OAuth2
themselves; the library does not implement Discord OAuth2.
- Configuration examples in the README and the docs use the camelCase keys the CodeLogic loader reads.
The PascalCase keys shown before are matched case-sensitively and were ignored, leaving defaults active.
- The live config section no longer says "CodeLogic 4.9+". The docs say the nonce is recorded after Steam confirms
the sign-in, list `social.steam.openid_busy`, the Discord and Steam profile error codes, and that only
`SendMessageAsync` / `SendEmbedAsync` apply the default username and avatar and only `GetPlayerAsync` publishes
`SteamProfileFetchedEvent`. Examples match on the result union (`r is SteamPlayer p`) instead of reading
`Value` without a null check. The README's trailing relative link, which broke on nuget.org, is gone.
## 2026-10-30
### Fixed
- **Health with every service switched off is Healthy** (`No services enabled`). It reported Unhealthy
"Not initialized — no services active", although services are only ever absent because the config
turns them off — an app that uses only some of the library (or none of it yet) showed as broken.
## 2026-10-26
### Added
- **Steam sign-in (OpenID 2.0)** for websites: `SteamOpenIdService`, exposed as `social.OpenId` with
`HasSteamOpenId`. `BuildLoginUrl(returnTo, realm?)` builds the "Sign in through Steam" redirect;
`VerifyAsync(query, expectedReturnTo)` checks the signed assertion locally (Steam's endpoint and
namespace, exact `return_to`, `claimed_id` = `identity` as a SteamID64, required signed fields, no
repeated parameters), then asks Steam to confirm it (`check_authentication`), and returns the
player's 64-bit Steam ID as `Result<SteamOpenIdResult>`. Publishes `SteamOpenIdVerifiedEvent`.
- `Steam.OpenIdEnabled` (default `true`): sign-in is available whenever Steam is enabled (which
requires `Steam.ApiKey`). It follows live config changes like the other Steam services: a Steam
settings change replaces `social.OpenId`, and it can be switched on or off without a restart.
## 2026-10-24
### Added
- **Live config changes** (requires CodeLogic 4.9+). `SocialConnectLibrary` implements
`IConfigReloadable`: a change to `config.socialconnect.json` made through `Libraries.UpdateConfigAsync`,
`Libraries.ReloadConfigAsync` or the config file watcher is applied without a restart, rebuilding only
the service whose settings changed (`Discord`, or `Steam` + `Auth`). Services and the library can be
switched on or off. A replaced service is disposed after its request timeout, so running requests finish.
## 2026-09-12
### Changed
- Unified the version line with the CodeLogic framework on **4.8.x**. Every official
library and the framework now share one `major.minor`, so a given `4.8.<patch>`
means the same generation across all packages.
- `version.txt` moved from `4.6` to `4.8`. The patch component remains the CI run
number, composed at pack time; `AssemblyVersion` stays pinned at `Major.Minor.0.0`
(now `4.8.0.0`) so every patch in the line loads interchangeably.
## 2026-06-20
### Fixed
- Discord sends now validate the 2000-character content limit and 10-embed limit
up front, returning a clear validation error instead of a failed HTTP
round-trip.
- Corrected the `CommunityVisibilityState` documentation comment (the Steam Web
API returns 1 = not visible / 3 = public to third parties; the previous
comment listed nonexistent levels).
### Documentation
- Full rewrite of the README and the `docs/libs/socialconnect.md` guide to the
current house style.
- README restructured to the standard layout (badges, tagline, intro, Install,
Quick start, Features, Configuration with table + JSON, Documentation,
Requirements, License) and now links the published docs page.
- Corrected the `Result` contract throughout: Discord methods return `Result`
(no value); Steam profile and auth methods return `Result<T>`. Examples use
`IsSuccess` / `IsFailure`, `.Value`, and `Error?.Message`.
- Documented the canonical load sequence (`LoadAsync` → `ConfigureAsync` →
`StartAsync` → `Libraries.Get`) and the `HasDiscord` / `HasSteam` /
`HasSteamAuth` guards (Steam disabled by default).
- Added a deep guide covering the three services, Discord webhook methods and
full model reference (with a rich-embed example), the Steam profile/bans/games
models and computed properties, Steam ticket authentication, caching
(`CacheTtlSeconds`, `ClearCache`), configuration, events, and the health check.
## [4.5.2] — 2026-06-20
### Documentation
- Corrected the README Quick Start: the text-message helper is
`Discord.SendMessageAsync(...)`, not `SendAsync(string, username:)`.
- Fixed the README configuration example to use PascalCase JSON keys and added the
previously omitted `DefaultAvatarUrl`, `AuthEnabled`, `AppId`, and `ApiBaseUrl` fields.
- Documented previously undocumented user-facing API surface:
- Discord `SendAsync(DiscordWebhookMessage)`, `DiscordAllowedMentions`
(`.None` / `.All`), and the `Tts` flag.
- Steam `SteamProfileService.ClearCache()` and the `GetOwnedGamesAsync`
`includeAppInfo` parameter.
- Per-call `appId` override on `Auth.AuthenticateAsync`.
- `SteamPlayer` (`IsPublic`, `IsInGame`, `AccountCreated`), `SteamPlayerBans`
(`HasAnyBan`), and `SteamGame` (`TotalPlaytime`, `RecentPlaytime`, `LastPlayed`,
`GetIconUrl`) computed helpers; the `DiscordUser` model.
- `HasDiscord` / `HasSteam` / `HasSteamAuth` availability properties and the
`InvalidOperationException` thrown when a disabled service is accessed.
- Event-bus events `WebhookSentEvent`, `SteamProfileFetchedEvent`, and
`SteamAuthenticatedEvent`, and the `SocialError` failure-code enum.
## [4.5.0] — 2026-05-24
### Changed
- **Unified versioning.** All CodeLogic.Libs now share a single version line
controlled by `version.txt` in the repo root. This is a version alignment
release — no functional changes to this library.
## [4.0.4] — 2026-04-16
### Changed
- README + manifest refresh for the v4 baseline. No functional changes vs 4.0.3.
- `LibraryManifest.Version` now reads from assembly metadata.
## [4.0.2] — 2026-04-09
### Changed
- Annotated SocialConnect configuration with `[ConfigField]` for the admin UI surface.
- Aligned with the v4 baseline across all libraries.
### Fixed
- Closed resource leaks in the OAuth client + reworked the health check so
it no longer mutates internal state as a side-effect.
## [4.0.0] — 2026-04-09
Major rewrite. Republished as v4.0.0 to reset the version line under the
unified v4 baseline. OAuth/OIDC providers (Google, Discord, Steam,
generic OIDC) with a uniform abstraction.
### Notes
- Earlier history is retained in the
[git log](https://github.com/zyntal-com/CodeLogic.Libs/commits/main/CL.SocialConnect).