diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 7296baad..0b3044a4 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -96,6 +96,10 @@ jobs:
with:
repository: vrchatapi/vrchatapi-csharp
+ # The artifact is overlaid onto the checkout rather than replacing it, so anything the
+ # generator stops emitting would otherwise survive in src/ forever.
+ - run: rm -rf src
+
- uses: actions/download-artifact@v8
with:
name: csharp-generated
diff --git a/.openapi-generator-ignore b/.openapi-generator-ignore
index 593af131..a97986c4 100644
--- a/.openapi-generator-ignore
+++ b/.openapi-generator-ignore
@@ -22,6 +22,16 @@
# Then explicitly reverse the ignore rule for a single file:
#!docs/README.md
+# Files the generator emits that this repository does not ship. Listing them here stops
+# them being written at all, rather than deleting them afterwards in generate.sh.
README.md
VRChat.API.sln
-src/VRChat.API.Test/
\ No newline at end of file
+src/VRChat.API.Test/**
+docs/**
+api/**
+appveyor.yml
+git_push.sh
+build.sh
+build.bat
+mono_nunit_test.sh
+nuget.exe
\ No newline at end of file
diff --git a/VRChat.API.sln b/VRChat.API.sln
index 8dabd6cf..ad51a884 100644
--- a/VRChat.API.sln
+++ b/VRChat.API.sln
@@ -4,8 +4,6 @@ VisualStudioVersion = 18.0.11205.157
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VRChat.API", "src\VRChat.API\VRChat.API.csproj", "{CFFD58F6-C881-46CB-BD52-445A8A8A8710}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VRChat.API.Wrapper", "wrapper\VRChat.API\VRChat.API.Wrapper.csproj", "{A38F1B7D-A1FF-B23E-08C3-81561FBF1C30}"
-EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VRChat.API.Extensions.Hosting", "wrapper\VRChat.API.Extensions.Hosting\VRChat.API.Extensions.Hosting.csproj", "{D9043377-EF21-E16A-7F4A-58D093467DC1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VRChat.API.Examples.AspNetCore", "examples\VRChat.API.Examples.AspNetCore\VRChat.API.Examples.AspNetCore.csproj", "{9F172233-1AF6-700F-5F54-423D12F46099}"
@@ -26,10 +24,6 @@ Global
{CFFD58F6-C881-46CB-BD52-445A8A8A8710}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CFFD58F6-C881-46CB-BD52-445A8A8A8710}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CFFD58F6-C881-46CB-BD52-445A8A8A8710}.Release|Any CPU.Build.0 = Release|Any CPU
- {A38F1B7D-A1FF-B23E-08C3-81561FBF1C30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {A38F1B7D-A1FF-B23E-08C3-81561FBF1C30}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {A38F1B7D-A1FF-B23E-08C3-81561FBF1C30}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {A38F1B7D-A1FF-B23E-08C3-81561FBF1C30}.Release|Any CPU.Build.0 = Release|Any CPU
{D9043377-EF21-E16A-7F4A-58D093467DC1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D9043377-EF21-E16A-7F4A-58D093467DC1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D9043377-EF21-E16A-7F4A-58D093467DC1}.Release|Any CPU.ActiveCfg = Release|Any CPU
diff --git a/generate.sh b/generate.sh
index a6f4ae72..b9044a72 100755
--- a/generate.sh
+++ b/generate.sh
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
+set -euo pipefail
if [ ${#} -le 1 ]
then
@@ -6,87 +7,53 @@ then
exit 1
fi
-rm src docs *.nupkg *.snupkg -rf
+spec="${1}"
+version="${2}"
+# Applies a sed expression to a file, but only after confirming the text it targets is still
+# there. sed exits 0 when it matches nothing, so without this guard a patch that stopped
+# applying after a generator upgrade would fail silently.
+patch_generated() {
+ local file="${1}" probe="${2}" expression="${3}"
+
+ if ! grep -qF "${probe}" "${file}"
+ then
+ echo "generate.sh: '${probe}' is no longer present in ${file}; the patch below is stale" >&2
+ exit 1
+ fi
+
+ sed -i "${expression}" "${file}"
+}
+
+rm -rf src docs ./*.nupkg ./*.snupkg
+
+# Output this repository does not ship is listed in .openapi-generator-ignore, so it is never
+# written. Everything VRChat-specific about the generated code lives in ./templates, and the
+# csproj customisation in ./templates/netcore_project.additions.mustache.
openapi-generator generate \
-g csharp \
-t ./templates \
--library httpclient \
---additional-properties=packageGuid=1c420561-97f1-4810-ad2d-cd344d27170a,packageName=VRChat.API,packageTags=vrchat,packageVersion="${2}",targetFramework=net8.0,licenseId=MIT,equatable=true \
+--additional-properties=packageGuid=1c420561-97f1-4810-ad2d-cd344d27170a,packageName=VRChat.API,packageVersion="${version}",targetFramework=net8.0,licenseId=MIT,equatable=true,packageAuthors="VRChat API Docs Community",packageCompany="VRChat API Docs Community",packageTitle="VRChat API Library for .NET",packageDescription="VRChat API Library for .NET",packageCopyright="Copyright © 2021 Owners of GitHub organisation \"vrchatapi\" and individual contributors." \
--git-user-id=vrchatapi \
--git-repo-id=vrchatapi-csharp \
-o . \
--i "${1}" \
+-i "${spec}" \
--http-user-agent="vrchatapi-csharp"
-rm build.sh
-rm build.bat
-rm git_push.sh
-rm mono_nunit_test.sh
-rm nuget.exe
-rm appveyor.yml
-
-rm -rf docs/
-rm -rf api/
-rm -rf src/VRChat.API.Test/
-
-# Move wrapper code to src/VRChat.API/Client/
-cp -r wrapper/VRChat.API/Client/* src/VRChat.API/Client/
-
cp wrapper/VRChat.API.Extensions.Hosting/vrc_cat.ico src/VRChat.API/vrc_cat.ico
cp wrapper/VRChat.API.Extensions.Hosting/vrc_cat.png src/VRChat.API/vrc_cat.png
-# Fix cookie handling
-for file in $(find ./src/VRChat.API -name '*.cs'); do
- sed -i 's/new Cookie("auth", this.Configuration.GetApiKeyWithPrefix("auth"))/new Cookie("auth", this.Configuration.GetApiKeyWithPrefix("auth"), "\/", "api.vrchat.cloud")/g' $file
- sed -i 's/new Cookie("twoFactorAuth", this.Configuration.GetApiKeyWithPrefix("twoFactorAuth"))/new Cookie("twoFactorAuth", this.Configuration.GetApiKeyWithPrefix("twoFactorAuth"), "\/", "api.vrchat.cloud")/g' $file
- sed -i 's/new Cookie(cookie.Name, cookie.Value)/new Cookie(cookie.Name, cookie.Value, cookie.Path, cookie.Domain)/g' $file
-done
-
-# Add ITwoFactorCode to two-factor authentication types
-sed -i 's/\(class.*IValidatableObject\)/\1, ITwoFactorCode/' src/VRChat.API/Model/TwoFactorAuthCode.cs
-sed -i 's/\(class.*IValidatableObject\)/\1, ITwoFactorCode/' src/VRChat.API/Model/TwoFactorEmailCode.cs
-
-# Add icons and readme to package
-sed -i ':a;N;$!ba;s|\(.*\)|\1\t \n\t \n\t True\n\t \\\n\t \n\t \n\t True\n\t \\\n\t \n |' src/VRChat.API/VRChat.API.csproj
-sed -i '/vrchat<\/PackageTags>/a\ vrc_cat.ico' src/VRChat.API/VRChat.API.csproj
-
-# Adjust package tags
-sed -i 's/vrchat<\/PackageTags>/vrchat,vrcapi,vrc-api,vrc<\/PackageTags>/' src/VRChat.API/VRChat.API.csproj
-
-# Fix username and password encoding
-sed -i 's/VRChat.API.Client.ClientUtils.Base64Encode(this.Configuration.Username + \":\" + this.Configuration.Password)/VRChat.API.Client.ClientUtils.Base64Encode(System.Web.HttpUtility.UrlEncode(this.Configuration.Username) + ":" + System.Web.HttpUtility.UrlEncode(this.Configuration.Password))/g' src/VRChat.API/Api/AuthenticationApi.cs
-
-# Fix fields in csproj
-sed -i 's/OpenAPI Library/VRChat API Library for .NET/' src/VRChat.API/VRChat.API.csproj
-sed -i 's/A library generated from a OpenAPI doc/VRChat API Library for .NET/' src/VRChat.API/VRChat.API.csproj
-sed -i 's/No Copyright/Copyright © 2021 Owners of GitHub organisation "vrchatapi" and individual contributors./' src/VRChat.API/VRChat.API.csproj
-sed -i 's/OpenAPI/VRChat API Docs Community/' src/VRChat.API/VRChat.API.csproj
-sed -i 's/Minor update/Automated deployment/' src/VRChat.API/VRChat.API.csproj
-
-# Fix failure to compile
-sed -i 's/false<\/GenerateAssemblyInfo>/true<\/GenerateAssemblyInfo>/' src/VRChat.API/VRChat.API.csproj
-
-# Update VRChat.API.Extensions.Hosting version
-sed -i "s|[^<]*|${2}|g" wrapper/VRChat.API.Extensions.Hosting/VRChat.API.Extensions.Hosting.csproj
-
-# Add README.md to fields
-sed -i '/PackageTags/a \ README.md<\/PackageReadmeFile>' src/VRChat.API/VRChat.API.csproj
-sed -i '/System.ComponentModel.Annotations/a \ ' src/VRChat.API/VRChat.API.csproj
-
-# Add Otp.NET package to project
-sed -i '/JsonSubTypes/a \ ' src/VRChat.API/VRChat.API.csproj
-
-# Make CurrentUser fields optional for 2FA response compatibility
-sed -i 's/IsRequired = true/IsRequired = false/g' src/VRChat.API/Model/CurrentUser.cs
-
-# Add RequiresTwoFactorAuth property to CurrentUser
-sed -i '/public string UserIcon { get; set; }/a\\n /// \n /// An array of two-factor authentication methods available to use to with two factor authentication.\n /// \n [DataMember(Name = "requiresTwoFactorAuth", IsRequired = false, EmitDefaultValue = true)]\n public List RequiresTwoFactorAuth { get; set; }' src/VRChat.API/Model/CurrentUser.cs
-
-# Remove messily pasted markdown at top of every file
-for i in src/VRChat.API/*/*.cs; do
- sed -i '/VRChat API Banner/d' $i
-done
-
cp README.md src/VRChat.API/
cp README.md src/
+
+# The only edit to generated code that no template or partial class can express: CurrentUser is
+# also returned mid-login, before two-factor verification, with most of its fields absent. The
+# specification marks them required, which makes deserialising that response throw, so the
+# generated attributes are relaxed for this one model.
+patch_generated src/VRChat.API/Model/CurrentUser.cs \
+ 'IsRequired = true' 's/IsRequired = true/IsRequired = false/g'
+
+# VRChat.API.Extensions.Hosting is hand-maintained, so its version is stamped rather than generated.
+patch_generated wrapper/VRChat.API.Extensions.Hosting/VRChat.API.Extensions.Hosting.csproj \
+ '' "s|[^<]*|${version}|g"
diff --git a/templates/WebRequestPathBuilder.mustache b/templates/WebRequestPathBuilder.mustache
index cc811ae4..a63cf080 100644
--- a/templates/WebRequestPathBuilder.mustache
+++ b/templates/WebRequestPathBuilder.mustache
@@ -18,11 +18,22 @@ namespace {{packageName}}.Client
_path = path;
}
+ ///
+ /// Percent-encodes a value, then restores "(" and ")".
+ ///
+ /// VRChat's API answers 400 "malformed url" when parentheses arrive
+ /// percent-encoded, and instance IDs contain them, for example
+ /// wrld_0000:12345~group(grp_0000)~groupAccessType(plus)~region(use).
+ private static string Escape(string value)
+ {
+ return Uri.EscapeDataString(value).Replace("%28", "(").Replace("%29", ")");
+ }
+
public void AddPathParameters(Dictionary parameters)
{
foreach (var parameter in parameters)
{
- _path = _path.Replace("{" + parameter.Key + "}", Uri.EscapeDataString(parameter.Value));
+ _path = _path.Replace("{" + parameter.Key + "}", Escape(parameter.Value));
}
}
@@ -32,7 +43,7 @@ namespace {{packageName}}.Client
{
foreach (var value in parameter.Value)
{
- _query = _query + parameter.Key + "=" + Uri.EscapeDataString(value) + "&";
+ _query = _query + parameter.Key + "=" + Escape(value) + "&";
}
}
}
diff --git a/templates/libraries/httpclient/api.mustache b/templates/libraries/httpclient/api.mustache
index 6b5b86e4..e5a6b72c 100644
--- a/templates/libraries/httpclient/api.mustache
+++ b/templates/libraries/httpclient/api.mustache
@@ -487,7 +487,7 @@ namespace {{packageName}}.{{apiPackage}}
// cookie parameter support
if (!string.IsNullOrEmpty(this.Configuration.GetApiKeyWithPrefix("{{keyParamName}}")))
{
- localVarRequestOptions.Cookies.Add(new Cookie("{{keyParamName}}", this.Configuration.GetApiKeyWithPrefix("{{keyParamName}}")));
+ localVarRequestOptions.Cookies.Add(new Cookie("{{keyParamName}}", this.Configuration.GetApiKeyWithPrefix("{{keyParamName}}"), "/", "api.vrchat.cloud"));
}
{{/isKeyInCookie}}
{{#isKeyInHeader}}
@@ -507,7 +507,7 @@ namespace {{packageName}}.{{apiPackage}}
// http basic authentication required
if (!string.IsNullOrEmpty(this.Configuration.Username) || !string.IsNullOrEmpty(this.Configuration.Password) && !localVarRequestOptions.HeaderParameters.ContainsKey("Authorization"))
{
- localVarRequestOptions.HeaderParameters.Add("Authorization", "Basic " + {{packageName}}.Client.ClientUtils.Base64Encode(this.Configuration.Username + ":" + this.Configuration.Password));
+ localVarRequestOptions.HeaderParameters.Add("Authorization", "Basic " + {{packageName}}.Client.ClientUtils.Base64Encode(System.Web.HttpUtility.UrlEncode(this.Configuration.Username) + ":" + System.Web.HttpUtility.UrlEncode(this.Configuration.Password)));
}
{{/isBasicBasic}}
{{#isBasicBearer}}
@@ -721,7 +721,7 @@ namespace {{packageName}}.{{apiPackage}}
// cookie parameter support
if (!string.IsNullOrEmpty(this.Configuration.GetApiKeyWithPrefix("{{keyParamName}}")))
{
- localVarRequestOptions.Cookies.Add(new Cookie("{{keyParamName}}", this.Configuration.GetApiKeyWithPrefix("{{keyParamName}}")));
+ localVarRequestOptions.Cookies.Add(new Cookie("{{keyParamName}}", this.Configuration.GetApiKeyWithPrefix("{{keyParamName}}"), "/", "api.vrchat.cloud"));
}
{{/isKeyInCookie}}
{{#isKeyInHeader}}
@@ -742,7 +742,7 @@ namespace {{packageName}}.{{apiPackage}}
// http basic authentication required
if (!string.IsNullOrEmpty(this.Configuration.Username) || !string.IsNullOrEmpty(this.Configuration.Password) && !localVarRequestOptions.HeaderParameters.ContainsKey("Authorization"))
{
- localVarRequestOptions.HeaderParameters.Add("Authorization", "Basic " + {{packageName}}.Client.ClientUtils.Base64Encode(this.Configuration.Username + ":" + this.Configuration.Password));
+ localVarRequestOptions.HeaderParameters.Add("Authorization", "Basic " + {{packageName}}.Client.ClientUtils.Base64Encode(System.Web.HttpUtility.UrlEncode(this.Configuration.Username) + ":" + System.Web.HttpUtility.UrlEncode(this.Configuration.Password)));
}
{{/isBasicBasic}}
{{#isBasicBearer}}
diff --git a/templates/modelGeneric.mustache b/templates/modelGeneric.mustache
index fca0622c..087dcd6c 100644
--- a/templates/modelGeneric.mustache
+++ b/templates/modelGeneric.mustache
@@ -364,13 +364,13 @@
return {{#vars}}{{#parent}}base.Equals(input) && {{/parent}}{{^isContainer}}
(
this.{{name}} == input.{{name}} ||
- {{^vendorExtensions.x-is-value-type}}
+ {{^vendorExtensions.x-csharp-value-type}}
(this.{{name}} != null &&
this.{{name}}.Equals(input.{{name}}))
- {{/vendorExtensions.x-is-value-type}}
- {{#vendorExtensions.x-is-value-type}}
+ {{/vendorExtensions.x-csharp-value-type}}
+ {{#vendorExtensions.x-csharp-value-type}}
this.{{name}}.Equals(input.{{name}})
- {{/vendorExtensions.x-is-value-type}}
+ {{/vendorExtensions.x-csharp-value-type}}
){{^-last}} && {{/-last}}{{/isContainer}}{{#isContainer}}
(
this.{{name}} == input.{{name}} ||
@@ -399,15 +399,15 @@
int hashCode = 41;
{{/parent}}
{{#vars}}
- {{^vendorExtensions.x-is-value-type}}
+ {{^vendorExtensions.x-csharp-value-type}}
if (this.{{name}} != null)
{
hashCode = (hashCode * 59) + this.{{name}}.GetHashCode();
}
- {{/vendorExtensions.x-is-value-type}}
- {{#vendorExtensions.x-is-value-type}}
+ {{/vendorExtensions.x-csharp-value-type}}
+ {{#vendorExtensions.x-csharp-value-type}}
hashCode = (hashCode * 59) + this.{{name}}.GetHashCode();
- {{/vendorExtensions.x-is-value-type}}
+ {{/vendorExtensions.x-csharp-value-type}}
{{/vars}}
{{#isAdditionalPropertiesTrue}}
if (this.AdditionalProperties != null)
diff --git a/templates/netcore_project.additions.mustache b/templates/netcore_project.additions.mustache
index 8c6f3ad5..69acd212 100644
--- a/templates/netcore_project.additions.mustache
+++ b/templates/netcore_project.additions.mustache
@@ -1 +1,39 @@
-{{! if needed users can add this file to their templates folder to append to the csproj }}
\ No newline at end of file
+{{!
+ Everything VRChat-specific about the generated csproj lives here, in the generator's own
+ extension point, rather than being sed-patched into the output afterwards. Rendered as the
+ last children of , so these PropertyGroup values override the ones above.
+}}
+
+
+ true
+
+ vrchat,vrcapi,vrc-api,vrc
+ Automated deployment
+ README.md
+ vrc_cat.ico
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ True
+ \
+
+
+ True
+ \
+
+
diff --git a/wrapper/VRChat.API.Extensions.Hosting/DefaultVRChatClientFactory.cs b/wrapper/VRChat.API.Extensions.Hosting/DefaultVRChatClientFactory.cs
index 5f07958d..db635867 100644
--- a/wrapper/VRChat.API.Extensions.Hosting/DefaultVRChatClientFactory.cs
+++ b/wrapper/VRChat.API.Extensions.Hosting/DefaultVRChatClientFactory.cs
@@ -72,9 +72,8 @@ public bool TryAddClient(string clientName, VRChatClientBuilder vcb, bool overri
{
if (_builders.ContainsKey(clientName) && !overrideIfExists) // So that the default may be registered in the case of it being nonexistant
return false;
- else
- _builders.Add(clientName, vcb);
+ _builders[clientName] = vcb; // Indexer rather than Add, so overrideIfExists actually overrides
return true;
}
}
diff --git a/wrapper/VRChat.API.Extensions.Hosting/IVRChatClientFactory.cs b/wrapper/VRChat.API.Extensions.Hosting/IVRChatClientFactory.cs
index 83bb93d8..597da234 100644
--- a/wrapper/VRChat.API.Extensions.Hosting/IVRChatClientFactory.cs
+++ b/wrapper/VRChat.API.Extensions.Hosting/IVRChatClientFactory.cs
@@ -50,6 +50,16 @@ public interface IVRChatClientFactory
///
Task LoginClientAsync(string name = "vrc_default", bool throwOnFail = false, CancellationToken ct = default);
+ ///
+ /// Registers a under a name, so
+ /// can later build a client from it.
+ ///
+ /// The name to register the builder under.
+ /// The to register.
+ /// Whether to replace an existing registration under the same name.
+ /// if the builder was registered; if a builder
+ /// is already registered under and
+ /// is .
bool TryAddClient(string clientName, VRChatClientBuilder vcb, bool overrideIfExists = false);
}
}
\ No newline at end of file
diff --git a/wrapper/VRChat.API/Client/VRChat.cs b/wrapper/VRChat.API/Client/VRChat.cs
index 9223ae29..fba7e622 100644
--- a/wrapper/VRChat.API/Client/VRChat.cs
+++ b/wrapper/VRChat.API/Client/VRChat.cs
@@ -203,6 +203,13 @@ public interface IVRChat
Task LoginWithExternalCodeAsync(Func, ITwoFactorCode> codeAction, CancellationToken ct = default);
}
+ ///
+ /// The default implementation, exposing every VRChat API surface through a
+ /// single client that shares one , cookie container and
+ /// .
+ ///
+ /// Instances are created through , or through
+ /// when you need to supply the underlying HTTP plumbing yourself.
public class VRChatClient : IVRChat
{
private VRChatClient(Configuration configuration, string twoFactorSecret = null, ApiClient client = null, HttpClient httpClient = null, HttpClientHandler httpClientHandler = null)
@@ -266,42 +273,93 @@ private VRChatClient(Configuration configuration, string twoFactorSecret = null,
private readonly PlayermoderationApi _moderations;
private readonly AuthenticationApi _authentication;
+ ///
+ /// The every API surface on this client was built with.
+ ///
public Configuration Configuration => _configuration;
+
+ ///
+ /// The that performs the underlying requests for every API surface on this client.
+ ///
public ApiClient Client => _client;
+
+ ///
+ /// The shared by every API surface on this client.
+ ///
public HttpClient HttpClient => _httpClient;
+
+ ///
+ /// The backing , whose
+ /// holds the session cookies returned by .
+ ///
public HttpClientHandler HttpClientHandler => _httpClientHandler;
+ ///
public IJamsApi Jams => _jams;
+ ///
public IFilesApi Files => _files;
+ ///
public IUsersApi Users => _users;
+ ///
public IPropsApi Props => _props;
+ ///
public IPrintsApi Prints => _prints;
+ ///
public IGroupsApi Groups => _groups;
+ ///
public IWorldsApi Worlds => _worlds;
+ ///
public IInviteApi Invites => _invites;
+ ///
public IAvatarsApi Avatars => _avatars;
+ ///
public IEconomyApi Economy => _economy;
+ ///
public IFriendsApi Friends => _friends;
+ ///
public ICalendarApi Calendar => _calendar;
+ ///
public IInventoryApi Inventory => _inventory;
+ ///
public IInstancesApi Instances => _instances;
+ ///
public IFavoritesApi Favorites => _favorites;
+ ///
public IMiscellaneousApi Miscellaneous => _miscellaneous;
+ ///
public INotificationsApi Notifications => _notifications;
+ ///
public IPlayermoderationApi Moderations => _moderations;
+ ///
public IAuthenticationApi Authentication => _authentication;
+ ///
public bool IsLoggedIn { get; private set; }
- // Creates a new VRChatClient, compatible with IVRChat
- public static VRChatClient Create(Configuration configuration, string twoFactorSecret, ApiClient client, HttpClient httpClient, HttpClientHandler handler) =>
- new VRChatClient(configuration, twoFactorSecret, client, httpClient, handler);
-
+ ///
+ /// Creates a new from pre-built components.
+ ///
+ /// Prefer unless you need to control the HTTP plumbing
+ /// yourself. Any argument left is replaced with a default instance, and a
+ /// yields a handler with cookies enabled.
+ /// The to build every API surface with.
+ /// The TOTP secret used by , or
+ /// if you intend to log in via .
+ /// The to issue requests through.
+ /// The to send requests with.
+ /// The handler backing ; its cookie
+ /// container carries the session returned by .
+ /// A new . It is not logged in until one of the login methods succeeds.
+ public static VRChatClient Create(Configuration configuration, string twoFactorSecret, ApiClient client, HttpClient httpClient, HttpClientHandler httpClientHandler) =>
+ new VRChatClient(configuration, twoFactorSecret, client, httpClient, httpClientHandler);
+
+ ///
public List GetCookies()
{
return _httpClientHandler.CookieContainer?.GetAllCookies().ToList();
}
+ ///
public async Task TryLoginAsync(CancellationToken ct = default)
{
CurrentUser user = null;
@@ -317,6 +375,7 @@ public async Task TryLoginAsync(CancellationToken ct = defaul
return new VRChatLoginResult(user == null, null);
}
+ ///
public async Task LoginAsync(CancellationToken ct = default)
{
if (_twoFactorSecret == null)
@@ -348,6 +407,7 @@ public async Task LoginAsync(CancellationToken ct = default)
return response.StatusCode == HttpStatusCode.OK ? user : null;
}
+ ///
public async Task LoginWithExternalCodeAsync(Func, ITwoFactorCode> codeAction, CancellationToken ct = default)
{
ApiResponse response = await this.Authentication.GetCurrentUserWithHttpInfoAsync(cancellationToken: ct);
diff --git a/wrapper/VRChat.API/Client/VRChatClientBuilder.cs b/wrapper/VRChat.API/Client/VRChatClientBuilder.cs
index 0d664542..8b7a6ce3 100644
--- a/wrapper/VRChat.API/Client/VRChatClientBuilder.cs
+++ b/wrapper/VRChat.API/Client/VRChatClientBuilder.cs
@@ -64,32 +64,31 @@ public static VRChatClientBuilder From(Configuration incomingConfiguration, ApiC
new VRChatClientBuilder(incomingConfiguration, incomingClient);
///
- ///
+ /// Sets the username, password and TOTP secret in one call.
///
- ///
- ///
- ///
- ///
- ///
+ /// The username to authenticate with.
+ /// The password to authenticate with.
+ /// The TOTP secret used to answer a two-factor challenge.
+ /// This , so calls can be chained.
public VRChatClientBuilder WithCredentials(string username, string password, string twoFactorSecret) => this
.WithUsername(username)
.WithPassword(password)
.WithTwoFactorSecret(twoFactorSecret);
///
- ///
+ /// Sets the username and password, for an account without two-factor authentication.
///
- ///
- ///
- ///
+ /// The username to authenticate with.
+ /// The password to authenticate with.
+ /// This , so calls can be chained.
public VRChatClientBuilder WithCredentials(string username, string password) =>
this.WithCredentials(username, password, null);
///
- ///
+ /// Sets the username to authenticate with.
///
- ///
- ///
+ /// The account's username. VRChat also accepts the email address.
+ /// This , so calls can be chained.
public VRChatClientBuilder WithUsername(string username)
{
_configuration.Username = username;
@@ -97,10 +96,10 @@ public VRChatClientBuilder WithUsername(string username)
}
///
- ///
+ /// Sets the password to authenticate with.
///
- ///
- ///
+ /// The account's password.
+ /// This , so calls can be chained.
public VRChatClientBuilder WithPassword(string password)
{
_configuration.Password = password;
@@ -108,10 +107,13 @@ public VRChatClientBuilder WithPassword(string password)
}
///
- ///
+ /// Sets the TOTP secret used to answer a two-factor challenge.
///
- ///
- ///
+ /// This is the base32 secret shown when two-factor authentication is set up, not a
+ /// six-digit code. It is required by ;
+ /// without it, use to supply codes yourself.
+ /// The account's base32 TOTP secret.
+ /// This , so calls can be chained.
public VRChatClientBuilder WithTwoFactorSecret(string twoFactorSecret)
{
_twoFactorSecret = twoFactorSecret;
@@ -119,27 +121,33 @@ public VRChatClientBuilder WithTwoFactorSecret(string twoFactorSecret)
}
///
- ///
+ /// Authenticates with cookies from an earlier session instead of a username and password.
///
- ///
- ///
- ///
+ /// Obtain these from on a logged-in client.
+ /// The auth cookie value.
+ /// The twoFactorAuth cookie value, which lets the session
+ /// skip the two-factor challenge.
+ /// This , so calls can be chained.
public VRChatClientBuilder WithAuthCookie(string auth, string twoFactorAuth = null)
{
+ // AddApiKey, not AddApiKeyPrefix: GetApiKeyWithPrefix returns `prefix + " " + value`, so
+ // storing a cookie as the prefix sent it with a trailing space and no value.
if (auth != null)
- _configuration.AddApiKeyPrefix("auth", auth);
+ _configuration.AddApiKey("auth", auth);
if (twoFactorAuth != null)
- _configuration.AddApiKeyPrefix("twoFactorAuth", auth);
+ _configuration.AddApiKey("twoFactorAuth", twoFactorAuth);
return this;
}
///
- ///
+ /// Sets the User-Agent header sent with every request.
///
- ///
- ///
+ /// VRChat requires a descriptive User-Agent identifying your application and a way to
+ /// contact you. Prefer , which formats one correctly.
+ /// The User-Agent to send, or for the default.
+ /// This , so calls can be chained.
public VRChatClientBuilder WithUserAgent(string userAgent)
{
_configuration.UserAgent = userAgent ?? _defaultUserAgent;
@@ -147,10 +155,10 @@ public VRChatClientBuilder WithUserAgent(string userAgent)
}
///
- ///
+ /// Sets how long a request may take before it is abandoned.
///
- ///
- ///
+ /// The request timeout.
+ /// This , so calls can be chained.
public VRChatClientBuilder WithTimeout(TimeSpan timeout)
{
_configuration.Timeout = timeout; // Using Miliseconds over TotalMilliseconds can cause issues when the timespan is empty
@@ -158,10 +166,10 @@ public VRChatClientBuilder WithTimeout(TimeSpan timeout)
}
///
- ///
+ /// Routes every request through a proxy.
///
- ///
- ///
+ /// The proxy to send requests through.
+ /// This , so calls can be chained.
public VRChatClientBuilder WithProxy(WebProxy proxy)
{
_configuration.Proxy = proxy;
@@ -169,17 +177,24 @@ public VRChatClientBuilder WithProxy(WebProxy proxy)
}
///
- ///
+ /// Routes every request through the proxy at a URL.
///
- ///
- ///
- ///
+ /// The proxy's address.
+ /// Whether to bypass the proxy for local addresses.
+ /// This , so calls can be chained.
public VRChatClientBuilder WithProxy(string url, bool bypass = true) =>
this.WithProxy(new WebProxy(url, bypass));
///
+ /// Sets the User-Agent from your application's details, in the form VRChat asks for.
///
+ /// Produces {name}/{version} ({contact}), VRChat.API/{libraryVersion}. VRChat may
+ /// block traffic whose User-Agent does not identify the application and a way to reach its author.
+ /// Your application's name.
+ /// Your application's version.
+ /// How VRChat can reach you, such as an email address or Discord handle.
+ /// This , so calls can be chained.
public VRChatClientBuilder WithApplication(string name, string version, string contact)
{
var libraryVersion = Assembly.GetExecutingAssembly().GetName().Version!.ToString();
@@ -188,11 +203,15 @@ public VRChatClientBuilder WithApplication(string name, string version, string c
}
///
- ///
+ /// Builds the configured client.
///
- ///
- ///
- ///
+ /// The returned client is not logged in. Call one of the login methods on
+ /// , or supply cookies through .
+ /// Whether to allow building a client that has no credentials,
+ /// which is valid for the endpoints that do not require authentication.
+ /// The configured client.
+ /// Thrown when is
+ /// and neither a username and password nor an auth cookie were set.
public IVRChat Build(bool useWithoutCredentials = true)
{
if (!useWithoutCredentials)
diff --git a/wrapper/VRChat.API/Model/GeneratedModelExtensions.cs b/wrapper/VRChat.API/Model/GeneratedModelExtensions.cs
new file mode 100644
index 00000000..36ebeca2
--- /dev/null
+++ b/wrapper/VRChat.API/Model/GeneratedModelExtensions.cs
@@ -0,0 +1,31 @@
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+
+namespace VRChat.API.Model
+{
+ ///
+ /// Additions to the generated , so it can be passed wherever a
+ /// two-factor code is accepted.
+ ///
+ public partial class TwoFactorAuthCode : ITwoFactorCode { }
+
+ ///
+ /// Additions to the generated , so it can be passed wherever a
+ /// two-factor code is accepted.
+ ///
+ public partial class TwoFactorEmailCode : ITwoFactorCode { }
+
+ ///
+ /// Additions to the generated .
+ ///
+ public partial class CurrentUser
+ {
+ ///
+ /// An array of two-factor authentication methods available to use to with two factor authentication.
+ ///
+ /// The API returns this field on the current-user response while a login is pending
+ /// two-factor verification, but the specification does not model it, so it is declared here.
+ [DataMember(Name = "requiresTwoFactorAuth", IsRequired = false, EmitDefaultValue = true)]
+ public List RequiresTwoFactorAuth { get; set; }
+ }
+}
diff --git a/wrapper/VRChat.API/VRChat.API.Wrapper.csproj b/wrapper/VRChat.API/VRChat.API.Wrapper.csproj
deleted file mode 100644
index dc196511..00000000
--- a/wrapper/VRChat.API/VRChat.API.Wrapper.csproj
+++ /dev/null
@@ -1,32 +0,0 @@
-
-
-
- false
- net8.0
- VRChat.API.Wrapper
- VRChat.API.Wrapper
- Library
- VRChat API Docs Community
- VRChat API Docs Community
- VRChat API Library for .NET
- VRChat API Library for .NET
- Copyright © 2021 Owners of GitHub organisation "vrchatapi" and individual contributors.
- VRChat.API
- 1.20.5
- bin\$(Configuration)\$(TargetFramework)\VRChat.API.xml
- MIT
- https://github.com/vrchatapi/vrchatapi-csharp.git
- git
- Automated deployment
- vrchat
- README.md
-
-
-
-
-
-
-
-
-
-