diff --git a/docs/AGENTS.md b/docs/AGENTS.md
new file mode 100644
index 000000000..426f823c9
--- /dev/null
+++ b/docs/AGENTS.md
@@ -0,0 +1,76 @@
+# AGENTS.md — Refitter
+
+OpenAPI-to-Refit code generator. Multi-package solution: CLI tool, MSBuild task, C# Source Generator, and Core library.
+
+## Build & Test
+
+- Solution: `src/Refitter.slnx`
+- SDK: `10.0.300` with `rollForward: latestFeature` (see `global.json`)
+- Build: `dotnet build -c Release src/Refitter.slnx`
+- Test: `dotnet test --solution src/Refitter.slnx -c Release`
+
+**Source Generator tests require a pre-build step.** Before running `dotnet test` on `Refitter.SourceGenerator.Tests`, the CI does:
+
+```bash
+rm -rf src/Refitter.SourceGenerator.Tests/AdditionalFiles/Generated
+dotnet restore src/Refitter.SourceGenerator.Tests/Refitter.SourceGenerator.Tests.csproj
+dotnet msbuild src/Refitter.SourceGenerator.Tests/Refitter.SourceGenerator.Tests.csproj
+```
+
+Run these first if source generator tests fail with missing generated types.
+
+## Package Boundaries
+
+| Project | TFM | Role |
+|---|---|---|
+| `Refitter.Core` | `netstandard2.0` | Core generation logic (NSwag-based) |
+| `Refitter` | `net8.0;net9.0;net10.0` | CLI tool (`PackAsTool`, container support) |
+| `Refitter.MSBuild` | `netstandard2.0` | MSBuild task; **requires Refitter CLI binaries** to be built first because it copies `../Refitter/bin/$(Configuration)/net{8,9,10}.0/**/*` into the package |
+| `Refitter.SourceGenerator` | `netstandard2.0` | Roslyn source generator; emits code in-memory via `AddSource()` (not to disk since v2.0.0) |
+| `Refitter.Tests` | `net10.0` | Unit tests |
+| `Refitter.SourceGenerator.Tests` | `net8.0;net10.0` | Source generator integration tests |
+
+## Testing
+
+- **Framework**: TUnit (not xUnit). Attributes: `[Test]`, `[Arguments(...)]`.
+- **Runner**: `Microsoft.Testing.Platform` (configured in `global.json`). Both test projects set `OutputType=Exe` and `GenerateProgramFile=false`.
+- **Unit test pattern**: Scenario tests under `Refitter.Tests.Scenarios` contain an OpenAPI spec as a `const string`, generate code via `RefitGenerator.CreateAsync(...).Generate()`, assert string patterns, and verify compilation with `BuildHelper.BuildCSharp(generatedCode)`.
+- **Temp file helper**: `SwaggerFileHelper.CreateSwaggerFile(contents)` creates a temp YAML file; `CreateSwaggerJsonFile` for JSON.
+- **Build helper**: `BuildHelper.BuildCSharp()` writes generated code to a temp project and invokes `dotnet build` to verify it compiles. It defaults to `net8.0` but supports `net9.0` and `net10.0`.
+- **Source generator tests**: Reference the generator as an `Analyzer` with `ReferenceOutputAssembly=false`. They verify generated types exist at runtime and have Refit HTTP attributes.
+
+## CI / Smoke Tests
+
+- Build workflow: `.github/workflows/build.yml`
+- Smoke tests: `.github/workflows/smoke-tests.yml` runs `test/smoke-tests.ps1`
+- The smoke test script publishes the CLI from source, generates clients against dozens of OpenAPI specs (v2.0, v3.0, v3.1, v3.4), and compiles them against console apps in `test/ConsoleApp/`.
+
+## Code Style
+
+- `.editorconfig` is authoritative.
+- 4-space indentation; `crlf` line endings for C# files per `.editorconfig`.
+- `var` is discouraged (`csharp_style_var_* = false:silent`).
+- `TreatWarningsAsErrors` is enabled on most production projects.
+
+## Key Conventions
+
+- **New features** must have unit tests in the `Refitter.Tests.Scenarios` namespace following the pattern: spec string → generate → assert → build.
+- **New CLI arguments** must be documented in `README.md` and in the `.refitter` file format docs.
+- **`.refitter` files** are the settings format for all three distribution forms (CLI, MSBuild, Source Generator).
+- **Source generator `.refitter` files** are automatically included as `AdditionalFiles` via the package props. The test project explicitly excludes `PropertyNamingPolicy.refitter` during design-time builds (`Condition="'$(DesignTimeBuild)' != 'true'"`).
+- **MSBuild** package includes a custom `.targets` file that runs `RefitterGenerateTask` before `BeforeCompile`.
+
+## Commit Discipline
+
+- **Commit as often as possible** in small, logical groups. Each commit should represent one coherent change (e.g., one file created, one bug fix, one adapter added).
+- **Build and run tests before every commit.** The minimum check is `dotnet build -c Release src/Refitter.slnx` followed by `dotnet test --solution src/Refitter.slnx -c Release --no-build`.
+- **Never commit broken code.** If tests fail, fix them before committing. If the fix requires additional changes, commit those together as a "fix: ..." commit.
+- **Commit messages** follow the pattern: `type: description` where type is one of `feat`, `fix`, `refactor`, `docs`, `test`, `chore`. Keep the subject line under 50 characters when possible.
+
+## Documentation
+
+- API docs are generated with DocFX from `docs/docfx_project/docfx.json`. To build locally:
+ ```bash
+ dotnet tool update -g docfx
+ docfx docs/docfx_project/docfx.json
+ ```
diff --git a/src/Refitter.Core/ByEndpointInterfacePartitioning.cs b/src/Refitter.Core/ByEndpointInterfacePartitioning.cs
new file mode 100644
index 000000000..60b9bcbea
--- /dev/null
+++ b/src/Refitter.Core/ByEndpointInterfacePartitioning.cs
@@ -0,0 +1,38 @@
+using System.Text;
+using NSwag;
+
+namespace Refitter.Core;
+
+internal class ByEndpointInterfacePartitioning(
+ RefitGeneratorSettings settings) : IInterfacePartitioning
+{
+
+ public string GetGroupKey(OpenApiOperationInfo operation) => $"{operation.Path}#{operation.Verb}";
+
+ public string GetInterfaceName(string groupKey, string title, string baseOperationName) =>
+ $"I{baseOperationName.CapitalizeFirstCharacter()}".Sanitize();
+
+ public string GetInterfaceNameSuffix() => "Endpoint";
+
+ public string GetMethodName(OpenApiOperationInfo operation, string interfaceName, string baseOperationName)
+ {
+ var methodName = !string.IsNullOrWhiteSpace(settings.OperationNameTemplate)
+ ? settings.OperationNameTemplate!.Replace("{operationName}", "Execute")
+ : "Execute";
+
+ return methodName;
+ }
+
+ public string GetDynamicQuerystringParameterType(string interfaceName, string methodName) =>
+ interfaceName.Substring(1).Replace("Endpoint", "QueryParams");
+
+ public bool IsSingleInterface => false;
+
+ public void AppendInterfaceDocumentation(
+ OpenApiDocument document,
+ XmlDocumentationGenerator docGenerator,
+ string groupKey,
+ OpenApiOperationInfo representativeOperation,
+ StringBuilder code) =>
+ docGenerator.AppendInterfaceDocumentationByEndpoint(representativeOperation.Operation, code);
+}
diff --git a/src/Refitter.Core/ByTagInterfacePartitioning.cs b/src/Refitter.Core/ByTagInterfacePartitioning.cs
new file mode 100644
index 000000000..ad32c1e24
--- /dev/null
+++ b/src/Refitter.Core/ByTagInterfacePartitioning.cs
@@ -0,0 +1,56 @@
+using System.Text;
+using NSwag;
+
+namespace Refitter.Core;
+
+internal class ByTagInterfacePartitioning : IInterfacePartitioning
+{
+ private readonly RefitGeneratorSettings settings;
+ private readonly string ungroupedTitle;
+
+ public ByTagInterfacePartitioning(RefitGeneratorSettings settings, OpenApiDocument document)
+ {
+ this.settings = settings;
+ ungroupedTitle = settings.Naming.UseOpenApiTitle
+ ? (document.Info?.Title ?? "ApiClient").Sanitize()
+ : settings.Naming.InterfaceName;
+ ungroupedTitle = ungroupedTitle.CapitalizeFirstCharacter();
+ }
+
+ public string GetGroupKey(OpenApiOperationInfo operation)
+ {
+ var tag = operation.Operation.Tags.FirstOrDefault();
+ return !string.IsNullOrWhiteSpace(tag)
+ ? tag.SanitizeControllerTag()
+ : ungroupedTitle;
+ }
+
+ public string GetInterfaceName(string groupKey, string title, string baseOperationName) =>
+ $"I{groupKey.CapitalizeFirstCharacter()}".Sanitize();
+
+ public string GetInterfaceNameSuffix() => "Api";
+
+ public string GetMethodName(OpenApiOperationInfo operation, string interfaceName, string baseOperationName)
+ {
+ var methodName = baseOperationName.CapitalizeFirstCharacter();
+ if (settings.OperationNameTemplate?.Contains("{operationName}") ?? false)
+ {
+ methodName = settings.OperationNameTemplate!.Replace("{operationName}", methodName);
+ }
+
+ return methodName;
+ }
+
+ public string GetDynamicQuerystringParameterType(string interfaceName, string methodName) =>
+ methodName + "QueryParams";
+
+ public bool IsSingleInterface => false;
+
+ public void AppendInterfaceDocumentation(
+ OpenApiDocument document,
+ XmlDocumentationGenerator docGenerator,
+ string groupKey,
+ OpenApiOperationInfo representativeOperation,
+ StringBuilder code) =>
+ docGenerator.AppendInterfaceDocumentationByTag(document, groupKey, code);
+}
diff --git a/src/Refitter.Core/IInterfacePartitioning.cs b/src/Refitter.Core/IInterfacePartitioning.cs
new file mode 100644
index 000000000..80e31bd2f
--- /dev/null
+++ b/src/Refitter.Core/IInterfacePartitioning.cs
@@ -0,0 +1,47 @@
+using System.Text;
+using NSwag;
+
+namespace Refitter.Core;
+
+internal interface IInterfacePartitioning
+{
+ ///
+ /// Gets the group key for an operation. Operations with the same key are placed in the same interface.
+ ///
+ string GetGroupKey(OpenApiOperationInfo operation);
+
+ ///
+ /// Gets the raw interface name for a group key. The InterfaceGenerator handles deduplication.
+ ///
+ string GetInterfaceName(string groupKey, string title, string baseOperationName);
+
+ ///
+ /// Gets the raw method name for an operation. The InterfaceGenerator handles deduplication.
+ ///
+ string GetMethodName(OpenApiOperationInfo operation, string interfaceName, string baseOperationName);
+
+ ///
+ /// Gets the dynamic querystring parameter type name for a method.
+ ///
+ string GetDynamicQuerystringParameterType(string interfaceName, string methodName);
+
+ ///
+ /// Gets the suffix appended to the interface name for deduplication.
+ ///
+ string GetInterfaceNameSuffix();
+
+ ///
+ /// True if this partitioning generates exactly one interface.
+ ///
+ bool IsSingleInterface { get; }
+
+ ///
+ /// Appends interface-level documentation to the StringBuilder.
+ ///
+ void AppendInterfaceDocumentation(
+ OpenApiDocument document,
+ XmlDocumentationGenerator docGenerator,
+ string groupKey,
+ OpenApiOperationInfo representativeOperation,
+ StringBuilder code);
+}
diff --git a/src/Refitter.Core/IRefitInterfaceGenerator.cs b/src/Refitter.Core/IRefitInterfaceGenerator.cs
deleted file mode 100644
index c7a012fbd..000000000
--- a/src/Refitter.Core/IRefitInterfaceGenerator.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace Refitter.Core;
-
-internal interface IRefitInterfaceGenerator
-{
- IEnumerable GenerateCode();
-}
diff --git a/src/Refitter.Core/InterfaceGenerator.cs b/src/Refitter.Core/InterfaceGenerator.cs
new file mode 100644
index 000000000..645f7fd58
--- /dev/null
+++ b/src/Refitter.Core/InterfaceGenerator.cs
@@ -0,0 +1,442 @@
+using System.Text;
+using System.Text.RegularExpressions;
+using NSwag;
+using NSwag.CodeGeneration.CSharp.Models;
+
+namespace Refitter.Core;
+
+internal class InterfaceGenerator
+{
+ private const string Separator = " ";
+ private static readonly Regex HttpResponseMessageTypeRegex = new("(Task|IObservable)", RegexOptions.Compiled, TimeSpan.FromSeconds(1));
+ private static readonly Regex ApiResponseTypeRegex = new("(Task|IObservable)<(I)?ApiResponse(<[\\w<>]+>)?>", RegexOptions.Compiled, TimeSpan.FromSeconds(1));
+
+ private readonly RefitGeneratorSettings settings;
+ private readonly OpenApiDocument document;
+ private readonly CustomCSharpClientGenerator generator;
+ private readonly XmlDocumentationGenerator docGenerator;
+
+ internal InterfaceGenerator(
+ RefitGeneratorSettings settings,
+ OpenApiDocument document,
+ CustomCSharpClientGenerator generator,
+ XmlDocumentationGenerator docGenerator)
+ {
+ this.settings = settings;
+ this.document = document;
+ this.generator = generator;
+ this.docGenerator = docGenerator;
+ generator.BaseSettings.OperationNameGenerator = new OperationNameGenerator(document, settings);
+ }
+
+ public IEnumerable Generate(IInterfacePartitioning partitioning)
+ {
+ var operations = document.Paths
+ .SelectMany(path => path.Value, (path, op) => new OpenApiOperationInfo(path.Key, op.Key, op.Value))
+ .ToList();
+
+ var groups = operations
+ .GroupBy(partitioning.GetGroupKey)
+ .ToList();
+
+ var knownInterfaceIdentifiers = new HashSet();
+ var title = settings.Naming.UseOpenApiTitle && !string.IsNullOrWhiteSpace(document.Info?.Title)
+ ? document.Info!.Title.Sanitize()
+ : settings.Naming.InterfaceName;
+
+ if (partitioning.IsSingleInterface)
+ {
+ var singleGroup = groups.Count > 0 ? groups[0].ToList() : new List();
+ yield return GenerateSingleInterface(singleGroup, partitioning, title, knownInterfaceIdentifiers);
+ }
+ else
+ {
+ foreach (var group in groups)
+ {
+ var nonDeprecatedOperations = group
+ .Where(op => settings.GenerateDeprecatedOperations || !op.Operation.IsDeprecated)
+ .ToList();
+
+ if (nonDeprecatedOperations.Count == 0)
+ continue;
+
+ foreach (var generatedCode in GenerateMultipleInterface(nonDeprecatedOperations, partitioning, title, knownInterfaceIdentifiers))
+ {
+ yield return generatedCode;
+ }
+ }
+ }
+ }
+
+ private GeneratedCode GenerateSingleInterface(
+ List operations,
+ IInterfacePartitioning partitioning,
+ string title,
+ HashSet knownInterfaceIdentifiers)
+ {
+ var baseOperationName = operations.Count > 0 ? GetBaseOperationName(operations[0]) : string.Empty;
+ var rawInterfaceName = partitioning.GetInterfaceName(string.Empty, title, baseOperationName);
+ var interfaceNameSuffix = partitioning.GetInterfaceNameSuffix();
+ var interfaceName = IdentifierUtils.Counted(knownInterfaceIdentifiers, rawInterfaceName, interfaceNameSuffix);
+
+ var code = new StringBuilder();
+ var dynamicQuerystringParametersCodeBuilder = new StringBuilder();
+ var representativeOperation = operations.Count > 0
+ ? operations[0]
+ : new OpenApiOperationInfo(string.Empty, string.Empty, new OpenApiOperation());
+ partitioning.AppendInterfaceDocumentation(document, docGenerator, string.Empty, representativeOperation, code);
+ var interfaceDeclaration = GenerateInterfaceDeclaration(interfaceName, partitioning.IsSingleInterface);
+ code.AppendLine(interfaceDeclaration);
+ code.AppendLine($"{Separator}{{");
+
+ var knownMethodIdentifiers = new HashSet();
+
+ foreach (var op in operations)
+ {
+ var (dynamicQuerystringParams, _) = GenerateMethod(op, interfaceName, partitioning, knownMethodIdentifiers, code);
+ if (!string.IsNullOrWhiteSpace(dynamicQuerystringParams))
+ {
+ dynamicQuerystringParametersCodeBuilder.AppendLine(dynamicQuerystringParams);
+ }
+ }
+
+ code.AppendLine($"{Separator}}}");
+ code.AppendLine(dynamicQuerystringParametersCodeBuilder.ToString());
+
+ return new GeneratedCode(interfaceName, code.ToString());
+ }
+
+ private IEnumerable GenerateMultipleInterface(
+ List operations,
+ IInterfacePartitioning partitioning,
+ string title,
+ HashSet knownInterfaceIdentifiers)
+ {
+ var representativeOperation = operations[0];
+ var baseOperationName = GetBaseOperationName(representativeOperation);
+ var groupKey = partitioning.GetGroupKey(representativeOperation);
+ var rawInterfaceName = partitioning.GetInterfaceName(groupKey, title, baseOperationName);
+ var interfaceNameSuffix = partitioning.GetInterfaceNameSuffix();
+ var interfaceName = IdentifierUtils.Counted(knownInterfaceIdentifiers, rawInterfaceName, interfaceNameSuffix);
+
+ var code = new StringBuilder();
+ partitioning.AppendInterfaceDocumentation(document, docGenerator, groupKey, representativeOperation, code);
+ var interfaceDeclaration = GenerateInterfaceDeclaration(interfaceName, partitioning.IsSingleInterface);
+ code.AppendLine(interfaceDeclaration);
+ code.AppendLine($"{Separator}{{");
+
+ var knownMethodIdentifiers = new HashSet();
+
+ foreach (var op in operations)
+ {
+ var (dynamicQuerystringParams, dynamicQuerystringType) = GenerateMethod(op, interfaceName, partitioning, knownMethodIdentifiers, code);
+ if (!string.IsNullOrWhiteSpace(dynamicQuerystringParams))
+ {
+ yield return new GeneratedCode(dynamicQuerystringType, dynamicQuerystringParams);
+ }
+ }
+
+ code.AppendLine($"{Separator}}}");
+
+ yield return new GeneratedCode(interfaceName, code.ToString());
+ }
+
+ private (string DynamicQuerystringParameters, string DynamicQuerystringParameterType) GenerateMethod(
+ OpenApiOperationInfo op,
+ string interfaceName,
+ IInterfacePartitioning partitioning,
+ HashSet knownMethodIdentifiers,
+ StringBuilder code)
+ {
+ var operation = op.Operation;
+
+ if (!settings.GenerateDeprecatedOperations && operation.IsDeprecated)
+ {
+ return (string.Empty, string.Empty);
+ }
+
+ var returnType = GetTypeName(operation);
+ var verb = op.Verb.CapitalizeFirstCharacter();
+ var baseOperationName = GetBaseOperationName(op);
+ var rawMethodName = partitioning.GetMethodName(op, interfaceName, baseOperationName);
+ var methodName = IdentifierUtils.Counted(knownMethodIdentifiers, rawMethodName);
+ knownMethodIdentifiers.Add(methodName);
+
+ var dynamicQuerystringParameterType = partitioning.GetDynamicQuerystringParameterType(interfaceName, methodName);
+ var operationModel = generator.CreateOperationModel(operation);
+ var parameters = ParameterExtractor.GetParameters(operationModel, operation, settings, dynamicQuerystringParameterType, out var operationDynamicQuerystringParameters).ToList();
+
+ var hasDynamicQuerystringParameter = !string.IsNullOrWhiteSpace(operationDynamicQuerystringParameters);
+ var parametersString = string.Join(", ", parameters);
+ var hasApizrRequestOptionsParameter = settings.ApizrSettings?.WithRequestOptions == true;
+ var hasCancellationToken = settings.UseCancellationTokens && !hasApizrRequestOptionsParameter;
+ var isApiResponseType = IsApiResponseType(returnType);
+
+ if (settings.GenerateXmlDocCodeComments)
+ {
+ docGenerator.AppendMethodDocumentation(operationModel, isApiResponseType, hasDynamicQuerystringParameter, hasApizrRequestOptionsParameter, hasCancellationToken, code);
+ }
+
+ GenerateObsoleteAttribute(operation, code);
+ GenerateForMultipartFormData(operationModel, code);
+ GenerateHeaders(operation, operationModel, code);
+
+ code.AppendLine($"{Separator}{Separator}[{verb}(\"{op.Path}\")]")
+ .AppendLine($"{Separator}{Separator}{returnType} {methodName}({parametersString});")
+ .AppendLine();
+
+ if (parametersString.Contains("?") && settings is { OptionalParameters: true, ApizrSettings: not null })
+ {
+ if (settings.GenerateXmlDocCodeComments)
+ {
+ docGenerator.AppendMethodDocumentation(operationModel, isApiResponseType, false, hasApizrRequestOptionsParameter, hasCancellationToken, code);
+ }
+
+ GenerateObsoleteAttribute(operation, code);
+ GenerateForMultipartFormData(operationModel, code);
+ GenerateHeaders(operation, operationModel, code);
+
+ parametersString = string.Join(", ", parameters.Where(parameter => !parameter.Contains("?")));
+
+ code.AppendLine($"{Separator}{Separator}[{verb}(\"{op.Path}\")]")
+ .AppendLine($"{Separator}{Separator}{returnType} {methodName}({parametersString});")
+ .AppendLine();
+ }
+
+ return (operationDynamicQuerystringParameters ?? string.Empty, dynamicQuerystringParameterType);
+ }
+
+ private string GetBaseOperationName(OpenApiOperationInfo op)
+ {
+ return generator
+ .BaseSettings
+ .OperationNameGenerator
+ .GetOperationName(document, op.Path, op.Verb, op.Operation);
+ }
+
+ private string GetTypeName(OpenApiOperation operation)
+ {
+ if (settings.ResponseTypeOverride.TryGetValue(operation.OperationId, out var type))
+ {
+ return type is null or "void"
+ ? GetAsyncOperationType(true)
+ : $"{GetAsyncOperationType(false)}<{WellKnownNamespaces.TrimImportedNamespaces(type)}>";
+ }
+
+ if (IsFileStreamResponse(operation))
+ {
+ return $"{GetAsyncOperationType(false)}";
+ }
+
+ var successCodes = new[] { "200", "201", "203", "206" };
+ var returnTypeParameter = successCodes
+ .Where(operation.Responses.ContainsKey)
+ .Select(code => GetTypeName(code, operation))
+ .FirstOrDefault();
+
+ if (returnTypeParameter == null && operation.Responses.ContainsKey("2XX"))
+ {
+ returnTypeParameter = GetTypeName("2XX", operation);
+ }
+
+ if (returnTypeParameter == null && operation.Responses.ContainsKey("default"))
+ {
+ returnTypeParameter = GetTypeName("default", operation);
+ }
+
+ return GetReturnType(returnTypeParameter);
+ }
+
+ private static bool IsFileStreamResponse(OpenApiOperation operation)
+ {
+ var successCodes = new[] { "200", "201", "203", "206", "2XX" };
+
+ foreach (var code in successCodes)
+ {
+ if (!operation.Responses.TryGetValue(code, out var apiResponse))
+ continue;
+
+ var response = apiResponse.ActualResponse;
+
+ if (response.Content?.Any() != true)
+ continue;
+
+ foreach (var contentEntry in response.Content)
+ {
+ if (IsFileContentType(contentEntry.Key))
+ {
+ var schema = contentEntry.Value?.Schema;
+ if (schema?.Format == "binary" || schema?.Type == NJsonSchema.JsonObjectType.File)
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private static bool IsFileContentType(string contentType)
+ {
+ return
+ contentType.StartsWith("application/octet-stream", StringComparison.OrdinalIgnoreCase) ||
+ contentType.StartsWith("application/pdf", StringComparison.OrdinalIgnoreCase) ||
+ contentType.StartsWith("application/vnd", StringComparison.OrdinalIgnoreCase) ||
+ contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase) ||
+ contentType.StartsWith("video/", StringComparison.OrdinalIgnoreCase) ||
+ contentType.StartsWith("audio/", StringComparison.OrdinalIgnoreCase) ||
+ contentType.StartsWith("application/zip", StringComparison.OrdinalIgnoreCase) ||
+ contentType.StartsWith("application/gzip", StringComparison.OrdinalIgnoreCase) ||
+ (contentType.StartsWith("application/x-", StringComparison.OrdinalIgnoreCase) &&
+ !contentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase));
+ }
+
+ private string GetTypeName(string code, OpenApiOperation operation)
+ {
+ var schema = operation.Responses[code].ActualResponse.Schema;
+ var typeName = generator.GetTypeName(schema, false, null);
+
+ if (!string.IsNullOrWhiteSpace(settings.CodeGeneratorSettings?.ArrayType) &&
+ schema?.Type == NJsonSchema.JsonObjectType.Array)
+ {
+ typeName = typeName
+ .Replace("ICollection", settings.CodeGeneratorSettings!.ArrayType)
+ .Replace("IEnumerable", settings.CodeGeneratorSettings!.ArrayType);
+ }
+
+ return typeName;
+ }
+
+ private string GetReturnType(string? returnTypeParameter)
+ {
+ return returnTypeParameter is null or "void"
+ ? GetDefaultReturnType()
+ : GetConfiguredReturnType(returnTypeParameter);
+ }
+
+ private string GetDefaultReturnType()
+ {
+ var asyncType = GetAsyncOperationType(true);
+ return settings.ReturnIApiResponse
+ ? $"{asyncType}"
+ : asyncType;
+ }
+
+ private string GetConfiguredReturnType(string returnTypeParameter)
+ {
+ var asyncType = GetAsyncOperationType(false);
+ return settings.ReturnIApiResponse
+ ? $"{asyncType}>"
+ : $"{asyncType}<{WellKnownNamespaces.TrimImportedNamespaces(returnTypeParameter)}>";
+ }
+
+ private string GetAsyncOperationType(bool withVoidReturnType)
+ {
+ var type = withVoidReturnType ? "" : string.Empty;
+ return settings.ReturnIObservable
+ ? "IObservable" + type
+ : "Task";
+ }
+
+ private static bool IsApiResponseType(string typeName)
+ {
+ return HttpResponseMessageTypeRegex.IsMatch(typeName) || ApiResponseTypeRegex.IsMatch(typeName);
+ }
+
+ private static void GenerateObsoleteAttribute(OpenApiOperation operation, StringBuilder code)
+ {
+ if (operation.IsDeprecated)
+ {
+ code.AppendLine($"{Separator}{Separator}[System.Obsolete]");
+ }
+ }
+
+ private static void GenerateForMultipartFormData(CSharpOperationModel operationModel, StringBuilder code)
+ {
+ if (operationModel.Consumes.Contains("multipart/form-data"))
+ {
+ code.AppendLine($"{Separator}{Separator}[Multipart]");
+ }
+ }
+
+ private void GenerateHeaders(
+ OpenApiOperation operation,
+ CSharpOperationModel operationModel,
+ StringBuilder code)
+ {
+ var headers = new List();
+
+ if (settings.AddAcceptHeaders && document.SchemaType is >= NJsonSchema.SchemaType.OpenApi3)
+ {
+ var uniqueContentTypes = new HashSet(StringComparer.OrdinalIgnoreCase);
+ foreach (var response in operation.Responses.Values)
+ {
+ if (response.Content == null)
+ continue;
+
+ foreach (var contentType in response.Content.Keys)
+ {
+ uniqueContentTypes.Add(contentType);
+ }
+ }
+
+ if (uniqueContentTypes.Any())
+ {
+ headers.Add($"\"Accept: {string.Join(", ", uniqueContentTypes)}\"");
+ }
+ }
+
+ if (settings.AddContentTypeHeaders && document.SchemaType is >= NJsonSchema.SchemaType.OpenApi3)
+ {
+ var uniqueContentTypes = operation.RequestBody?.Content.Keys ?? Array.Empty();
+ var contentType =
+ uniqueContentTypes.FirstOrDefault(c => c.Equals("application/json", StringComparison.OrdinalIgnoreCase)) ??
+ uniqueContentTypes.FirstOrDefault();
+
+ if (!string.IsNullOrWhiteSpace(contentType) && !operationModel.Consumes.Contains("multipart/form-data"))
+ {
+ headers.Add($"\"Content-Type: {contentType}\"");
+ }
+ }
+
+ if (settings.AuthenticationHeaderStyle == AuthenticationHeaderStyle.Method)
+ {
+ foreach (var securitySchemeName in operationModel.Security.SelectMany(x => x.Keys))
+ {
+ if ((settings.SecurityScheme != null && securitySchemeName != settings.SecurityScheme) ||
+ !document.SecurityDefinitions.TryGetValue(securitySchemeName, out var securityScheme))
+ {
+ continue;
+ }
+
+ if (securityScheme is { Type: OpenApiSecuritySchemeType.Http, Scheme: var scheme }
+ && string.Equals(scheme, "bearer", StringComparison.OrdinalIgnoreCase))
+ {
+ headers.Add("\"Authorization: Bearer\"");
+ }
+ }
+ }
+
+ if (headers.Any())
+ {
+ code.AppendLine($"{Separator}{Separator}[Headers({string.Join(", ", headers)})]");
+ }
+ }
+
+ private string GenerateInterfaceDeclaration(string interfaceName, bool isSingleInterface)
+ {
+ var inheritance = isSingleInterface && settings.GenerateDisposableClients
+ ? " : IDisposable"
+ : null;
+
+ var modifier = settings.TypeAccessibility.ToString().ToLowerInvariant();
+ return $"""
+ {Separator}{GetGeneratedCodeAttribute()}
+ {Separator}{modifier} partial interface {interfaceName}{inheritance}
+ """;
+ }
+
+ private string GetGeneratedCodeAttribute() =>
+ $"""
+ [System.CodeDom.Compiler.GeneratedCode("Refitter", "{GetType().Assembly.GetName().Version}")]
+ """;
+}
diff --git a/src/Refitter.Core/OpenApiOperationInfo.cs b/src/Refitter.Core/OpenApiOperationInfo.cs
new file mode 100644
index 000000000..81c2b80bc
--- /dev/null
+++ b/src/Refitter.Core/OpenApiOperationInfo.cs
@@ -0,0 +1,5 @@
+using NSwag;
+
+namespace Refitter.Core;
+
+internal record OpenApiOperationInfo(string Path, string Verb, OpenApiOperation Operation);
diff --git a/src/Refitter.Core/RefitGenerator.cs b/src/Refitter.Core/RefitGenerator.cs
index f1119b153..a36bf8f02 100644
--- a/src/Refitter.Core/RefitGenerator.cs
+++ b/src/Refitter.Core/RefitGenerator.cs
@@ -153,7 +153,7 @@ public string Generate()
// (pre-generation) operation IDs. GenerateFile() auto-populates operation IDs
// with globally unique names which would prevent the switch to the path segments
// generator, causing unnecessary numeric suffixes in ByTag mode.
- var interfaceGenerator = CreateInterfaceGenerator(generator, docGenerator);
+ var interfaceGenerator = new InterfaceGenerator(settings, document, generator, docGenerator);
var contracts = generator.GenerateFile();
contracts = SanitizeGeneratedContracts(contracts);
@@ -210,7 +210,7 @@ public GeneratorOutput GenerateMultipleFiles()
// (pre-generation) operation IDs. GenerateFile() auto-populates operation IDs
// with globally unique names which would prevent the switch to the path segments
// generator, causing unnecessary numeric suffixes in ByTag mode.
- var interfaceGenerator = CreateInterfaceGenerator(generator, docGenerator);
+ var interfaceGenerator = new InterfaceGenerator(settings, document, generator, docGenerator);
var contracts = generator.GenerateFile();
contracts = SanitizeGeneratedContracts(contracts);
@@ -271,19 +271,13 @@ public GeneratorOutput GenerateMultipleFiles()
return new GeneratorOutput(generatedFiles);
}
- ///
- /// Creates the appropriate interface generator based on settings.
- /// This must be called before GenerateFile() to ensure operation ID detection works correctly.
- ///
- private IRefitInterfaceGenerator CreateInterfaceGenerator(
- CustomCSharpClientGenerator generator,
- XmlDocumentationGenerator docGenerator)
+ private IInterfacePartitioning GetInterfacePartitioning()
{
return settings.MultipleInterfaces switch
{
- MultipleInterfaces.ByEndpoint => new RefitMultipleInterfaceGenerator(settings, document, generator, docGenerator),
- MultipleInterfaces.ByTag => new RefitMultipleInterfaceByTagGenerator(settings, document, generator, docGenerator),
- _ => new RefitInterfaceGenerator(settings, document, generator, docGenerator),
+ MultipleInterfaces.ByEndpoint => new ByEndpointInterfacePartitioning(settings),
+ MultipleInterfaces.ByTag => new ByTagInterfacePartitioning(settings, document),
+ _ => new SingleInterfacePartitioning(settings),
};
}
@@ -368,7 +362,7 @@ private string GenerateJsonSerializerContext(string contracts) =>
///
/// The interface generator used to generate the client code.
/// The generated client code as a string.
- private IReadOnlyCollection GenerateClient(IRefitInterfaceGenerator interfaceGenerator)
+ private IReadOnlyCollection GenerateClient(InterfaceGenerator interfaceGenerator)
{
var code = new StringBuilder();
GenerateAutoGeneratedHeader(code);
@@ -390,7 +384,8 @@ private IReadOnlyCollection GenerateClient(IRefitInterfaceGenerat
code.AppendLine("#nullable enable annotations");
code.AppendLine();
- var refitInterfaces = interfaceGenerator.GenerateCode();
+ var partitioning = GetInterfacePartitioning();
+ var refitInterfaces = interfaceGenerator.Generate(partitioning);
var generatedCodes = refitInterfaces as GeneratedCode[] ?? refitInterfaces.ToArray();
if (settings.GenerateMultipleFiles)
diff --git a/src/Refitter.Core/RefitInterfaceGenerator.cs b/src/Refitter.Core/RefitInterfaceGenerator.cs
deleted file mode 100644
index bdfcb8aa9..000000000
--- a/src/Refitter.Core/RefitInterfaceGenerator.cs
+++ /dev/null
@@ -1,390 +0,0 @@
-using System.Text;
-using System.Text.RegularExpressions;
-using NSwag;
-using NSwag.CodeGeneration.CSharp.Models;
-
-namespace Refitter.Core;
-
-internal class RefitInterfaceGenerator : IRefitInterfaceGenerator
-{
- protected const string Separator = " ";
- private static readonly Regex HttpResponseMessageTypeRegex = new("(Task|IObservable)", RegexOptions.Compiled, TimeSpan.FromSeconds(1));
- private static readonly Regex ApiResponseTypeRegex = new("(Task|IObservable)<(I)?ApiResponse(<[\\w<>]+>)?>", RegexOptions.Compiled, TimeSpan.FromSeconds(1));
-
- protected readonly RefitGeneratorSettings settings;
- protected readonly OpenApiDocument document;
- protected readonly CustomCSharpClientGenerator generator;
- protected readonly XmlDocumentationGenerator docGenerator;
-
- internal RefitInterfaceGenerator(
- RefitGeneratorSettings settings,
- OpenApiDocument document,
- CustomCSharpClientGenerator generator,
- XmlDocumentationGenerator docGenerator)
- {
- this.settings = settings;
- this.document = document;
- this.generator = generator;
- this.docGenerator = docGenerator;
- generator.BaseSettings.OperationNameGenerator = new OperationNameGenerator(document, settings);
- }
-
- public virtual IEnumerable GenerateCode()
- {
- var interfaceDeclaration = GenerateInterfaceDeclaration(out var interfaceName);
- yield return new GeneratedCode(
- interfaceName,
- $$"""
- {{interfaceDeclaration}}
- {{Separator}}{
- {{GenerateInterfaceBody(out var dynamicQuerystringParameters)}}
- {{Separator}}}
- {{dynamicQuerystringParameters}}
- """);
- }
-
- private string GenerateInterfaceBody(out string? dynamicQuerystringParameters)
- {
- var code = new StringBuilder();
- var dynamicQuerystringParametersCodeBuilder = new StringBuilder();
- var knownIdentifiers = new HashSet();
- foreach (var kv in document.Paths)
- {
- foreach (var operations in kv.Value)
- {
- var operation = operations.Value;
-
- if (!settings.GenerateDeprecatedOperations && operation.IsDeprecated)
- {
- continue;
- }
-
- var returnType = GetTypeName(operation);
- var verb = operations.Key.CapitalizeFirstCharacter();
- var operationName = IdentifierUtils.Counted(knownIdentifiers, GenerateOperationName(kv.Key, verb, operation));
- knownIdentifiers.Add(operationName);
- var dynamicQuerystringParameterType = operationName + "QueryParams";
-
- var operationModel = generator.CreateOperationModel(operation);
- var parameters = ParameterExtractor.GetParameters(operationModel, operation, settings, dynamicQuerystringParameterType, out var operationDynamicQuerystringParameters).ToList();
-
- var hasDynamicQuerystringParameter = !string.IsNullOrWhiteSpace(operationDynamicQuerystringParameters);
- if (hasDynamicQuerystringParameter)
- dynamicQuerystringParametersCodeBuilder.AppendLine(operationDynamicQuerystringParameters);
-
- var parametersString = string.Join(", ", parameters);
- var hasApizrRequestOptionsParameter = settings.ApizrSettings?.WithRequestOptions == true;
- var hasCancellationToken = settings.UseCancellationTokens && !hasApizrRequestOptionsParameter;
- var isApiResponseType = IsApiResponseType(returnType);
-
- if (settings.GenerateXmlDocCodeComments)
- {
- this.docGenerator.AppendMethodDocumentation(operationModel, isApiResponseType, hasDynamicQuerystringParameter, hasApizrRequestOptionsParameter, hasCancellationToken, code);
- }
-
- GenerateObsoleteAttribute(operation, code);
- GenerateForMultipartFormData(operationModel, code);
- GenerateHeaders(operations, operationModel, code);
-
- code.AppendLine($"{Separator}{Separator}[{verb}(\"{kv.Key}\")]")
- .AppendLine($"{Separator}{Separator}{returnType} {operationName}({parametersString});")
- .AppendLine();
-
- if (parametersString.Contains("?") && settings is { OptionalParameters: true, ApizrSettings: not null })
- {
- if (settings.GenerateXmlDocCodeComments)
- {
- this.docGenerator.AppendMethodDocumentation(operationModel, isApiResponseType, false, hasApizrRequestOptionsParameter, hasCancellationToken, code);
- }
- GenerateObsoleteAttribute(operation, code);
- GenerateForMultipartFormData(operationModel, code);
- GenerateHeaders(operations, operationModel, code);
-
- parametersString = string.Join(", ", parameters.Where(parameter => !parameter.Contains("?")));
-
- code.AppendLine($"{Separator}{Separator}[{verb}(\"{kv.Key}\")]")
- .AppendLine($"{Separator}{Separator}{returnType} {operationName}({parametersString});")
- .AppendLine();
- }
- }
- }
-
- dynamicQuerystringParameters = dynamicQuerystringParametersCodeBuilder.ToString();
-
- return code.ToString();
- }
-
- protected string GetTypeName(OpenApiOperation operation)
- {
- if (settings.ResponseTypeOverride.TryGetValue(operation.OperationId, out var type))
- {
- return type is null or "void" ? GetAsyncOperationType(true) : $"{GetAsyncOperationType(false)}<{WellKnownNamespaces.TrimImportedNamespaces(type)}>";
- }
-
- // Check if response is a file stream
- if (IsFileStreamResponse(operation))
- {
- return $"{GetAsyncOperationType(false)}";
- }
-
- // First check for explicit success status codes
- var successCodes = new[] { "200", "201", "203", "206" };
- var returnTypeParameter = successCodes
- .Where(operation.Responses.ContainsKey)
- .Select(code => GetTypeName(code, operation))
- .FirstOrDefault();
-
- // If no explicit success codes found, check for 2XX range
- if (returnTypeParameter == null && operation.Responses.ContainsKey("2XX"))
- {
- returnTypeParameter = GetTypeName("2XX", operation);
- }
-
- // If no success codes or ranges found, check for default response
- if (returnTypeParameter == null && operation.Responses.ContainsKey("default"))
- {
- returnTypeParameter = GetTypeName("default", operation);
- }
-
- return GetReturnType(returnTypeParameter);
- }
-
- ///
- /// Determines if the operation response is a file stream (binary content).
- ///
- /// The OpenAPI operation to check.
- /// True if the response is a file stream, false otherwise.
- private static bool IsFileStreamResponse(OpenApiOperation operation)
- {
- var successCodes = new[] { "200", "201", "203", "206", "2XX" };
-
- foreach (var code in successCodes)
- {
- if (!operation.Responses.TryGetValue(code, out var apiResponse))
- continue;
-
- var response = apiResponse.ActualResponse;
-
- if (response.Content?.Any() != true)
- continue;
-
- foreach (var contentEntry in response.Content)
- {
- if (IsFileContentType(contentEntry.Key))
- {
- var schema = contentEntry.Value?.Schema;
- if (schema?.Format == "binary" || schema?.Type == NJsonSchema.JsonObjectType.File)
- return true;
- }
- }
- }
-
- return false;
- }
-
- private static bool IsFileContentType(string contentType)
- {
- return
- contentType.StartsWith("application/octet-stream", StringComparison.OrdinalIgnoreCase) ||
- contentType.StartsWith("application/pdf", StringComparison.OrdinalIgnoreCase) ||
- contentType.StartsWith("application/vnd", StringComparison.OrdinalIgnoreCase) ||
- contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase) ||
- contentType.StartsWith("video/", StringComparison.OrdinalIgnoreCase) ||
- contentType.StartsWith("audio/", StringComparison.OrdinalIgnoreCase) ||
- contentType.StartsWith("application/zip", StringComparison.OrdinalIgnoreCase) ||
- contentType.StartsWith("application/gzip", StringComparison.OrdinalIgnoreCase) ||
- (contentType.StartsWith("application/x-", StringComparison.OrdinalIgnoreCase) &&
- !contentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase));
- }
-
- private string GetTypeName(string code, OpenApiOperation operation)
- {
- var schema = operation.Responses[code].ActualResponse.Schema;
- var typeName = generator.GetTypeName(schema, false, null);
-
- if (!string.IsNullOrWhiteSpace(settings.CodeGeneratorSettings?.ArrayType) &&
- schema?.Type == NJsonSchema.JsonObjectType.Array)
- {
- typeName = typeName
- .Replace("ICollection", settings.CodeGeneratorSettings!.ArrayType)
- .Replace("IEnumerable", settings.CodeGeneratorSettings!.ArrayType);
- }
-
- return typeName;
- }
-
- protected string GenerateOperationName(
- string path,
- string verb,
- OpenApiOperation operation,
- bool capitalizeFirstCharacter = false)
- {
- const string operationNamePlaceholder = "{operationName}";
-
- var operationName = generator
- .BaseSettings
- .OperationNameGenerator
- .GetOperationName(document, path, verb, operation);
-
- if (capitalizeFirstCharacter)
- operationName = operationName.CapitalizeFirstCharacter();
-
- if (settings.OperationNameTemplate?.Contains(operationNamePlaceholder) ?? false)
- {
- operationName = settings.OperationNameTemplate!
- .Replace(operationNamePlaceholder, operationName);
- }
-
- return operationName;
- }
-
- protected static void GenerateForMultipartFormData(CSharpOperationModel operationModel, StringBuilder code)
- {
- if (operationModel.Consumes.Contains("multipart/form-data"))
- {
- code.AppendLine($"{Separator}{Separator}[Multipart]");
- }
- }
-
- protected void GenerateHeaders(
- KeyValuePair operations,
- CSharpOperationModel operationModel,
- StringBuilder code)
- {
- var headers = new List();
-
- if (settings.AddAcceptHeaders && document.SchemaType is >= NJsonSchema.SchemaType.OpenApi3)
- {
- //Generate header "Accept"
- var uniqueContentTypes = new HashSet(StringComparer.OrdinalIgnoreCase);
- foreach (var response in operations.Value.Responses.Values)
- {
- if (response.Content == null)
- continue;
-
- foreach (var contentType in response.Content.Keys)
- {
- uniqueContentTypes.Add(contentType);
- }
- }
-
- if (uniqueContentTypes.Any())
- {
- headers.Add($"\"Accept: {string.Join(", ", uniqueContentTypes)}\"");
- }
- }
-
- if (settings.AddContentTypeHeaders && document.SchemaType is >= NJsonSchema.SchemaType.OpenApi3)
- {
- var uniqueContentTypes = operations.Value.RequestBody?.Content.Keys ?? Array.Empty();
- var contentType =
- uniqueContentTypes.FirstOrDefault(c => c.Equals("application/json", StringComparison.OrdinalIgnoreCase)) ??
- uniqueContentTypes.FirstOrDefault();
-
- if (!string.IsNullOrWhiteSpace(contentType) && !operationModel.Consumes.Contains("multipart/form-data"))
- {
- headers.Add($"\"Content-Type: {contentType}\"");
- }
- }
-
- if (settings.AuthenticationHeaderStyle == AuthenticationHeaderStyle.Method)
- {
- foreach (var securitySchemeName in operationModel.Security.SelectMany(x => x.Keys))
- {
- if ((settings.SecurityScheme != null && securitySchemeName != settings.SecurityScheme) ||
- !document.SecurityDefinitions.TryGetValue(securitySchemeName, out var securityScheme))
- {
- continue;
- }
-
- if (securityScheme is { Type: OpenApiSecuritySchemeType.Http, Scheme: var scheme }
- && string.Equals(scheme, "bearer", StringComparison.OrdinalIgnoreCase))
- {
- headers.Add("\"Authorization: Bearer\"");
- }
- }
- }
-
- if (headers.Any())
- {
- code.AppendLine($"{Separator}{Separator}[Headers({string.Join(", ", headers)})]");
- }
- }
-
- protected string GetReturnType(string? returnTypeParameter)
- {
- return returnTypeParameter is null or "void"
- ? GetDefaultReturnType()
- : GetConfiguredReturnType(returnTypeParameter);
- }
-
- private string GetDefaultReturnType()
- {
- var asyncType = GetAsyncOperationType(true);
- return settings.ReturnIApiResponse
- ? $"{asyncType}"
- : asyncType;
- }
-
- ///
- /// Checks if the given return type is derived from ApiResponse or its interface.
- ///
- /// The name of the type to check.
- /// True if the type is an ApiResponse Task or similar, false otherwise.
- protected static bool IsApiResponseType(string typeName)
- {
- return HttpResponseMessageTypeRegex.IsMatch(typeName) || ApiResponseTypeRegex.IsMatch(typeName);
- }
-
- private string GetConfiguredReturnType(string returnTypeParameter)
- {
- var asyncType = GetAsyncOperationType(false);
- return settings.ReturnIApiResponse
- ? $"{asyncType}>"
- : $"{asyncType}<{WellKnownNamespaces.TrimImportedNamespaces(returnTypeParameter)}>";
- }
-
- private string GetAsyncOperationType(bool withVoidReturnType)
- {
- var type = withVoidReturnType ? "" : string.Empty;
- return settings.ReturnIObservable
- ? "IObservable" + type
- : "Task";
- }
-
- protected void GenerateObsoleteAttribute(OpenApiOperation operation, StringBuilder code)
- {
- if (operation.IsDeprecated)
- {
- code.AppendLine($"{Separator}{Separator}[System.Obsolete]");
- }
- }
-
- private string GenerateInterfaceDeclaration(out string interfaceName)
- {
- var title = settings.Naming.UseOpenApiTitle
- ? (document.Info?.Title ?? NamingSettings.DefaultInterfaceName)
- : settings.Naming.InterfaceName;
-
- // Sanitize after prefixing to prevent I@keyword pattern (#1053)
- interfaceName = $"I{title.CapitalizeFirstCharacter()}".Sanitize();
-
- var inheritance = settings.GenerateDisposableClients
- ? " : IDisposable"
- : null;
-
- var modifier = settings.TypeAccessibility.ToString().ToLowerInvariant();
- var code = new StringBuilder();
- docGenerator.AppendSingleInterfaceDocumentation(document, code);
- code.Append($"""
- {Separator}{GetGeneratedCodeAttribute()}
- {Separator}{modifier} partial interface {interfaceName}{inheritance}
- """);
- return code.ToString();
- }
-
- protected string GetGeneratedCodeAttribute() =>
- $"""
- [System.CodeDom.Compiler.GeneratedCode("Refitter", "{GetType().Assembly.GetName().Version}")]
- """;
-}
diff --git a/src/Refitter.Core/RefitMultipleInterfaceByTagGenerator.cs b/src/Refitter.Core/RefitMultipleInterfaceByTagGenerator.cs
deleted file mode 100644
index bb398548b..000000000
--- a/src/Refitter.Core/RefitMultipleInterfaceByTagGenerator.cs
+++ /dev/null
@@ -1,189 +0,0 @@
-using System.Text;
-using NSwag;
-
-namespace Refitter.Core;
-
-internal class RefitMultipleInterfaceByTagGenerator : RefitInterfaceGenerator
-{
- private readonly HashSet knownIdentifiers = new();
- private Dictionary>? _methodIdentifiersByInterface;
-
- internal RefitMultipleInterfaceByTagGenerator(
- RefitGeneratorSettings settings,
- OpenApiDocument document,
- CustomCSharpClientGenerator generator,
- XmlDocumentationGenerator docGenerator)
- : base(settings, document, generator, docGenerator)
- {
- }
-
- public override IEnumerable GenerateCode()
- {
- var ungroupedTitle = settings.Naming.UseOpenApiTitle
- ? IdentifierUtils.Sanitize(document.Info?.Title ?? "ApiClient")
- : settings.Naming.InterfaceName;
- ungroupedTitle = ungroupedTitle.CapitalizeFirstCharacter();
-
- var byGroup = document.Paths
- .SelectMany(x => x.Value, (k, v) => (PathItem: k, Operation: v))
- .GroupBy(x => GetGroupName(x.Operation.Value, ungroupedTitle), (k, v) => new { Key = k, Combined = v });
-
- Dictionary interfacesByGroup = new();
- Dictionary interfacesNamesByGroup = new();
-
- _methodIdentifiersByInterface = new();
-
- foreach (var kv in byGroup)
- {
- foreach (var op in kv.Combined)
- {
- var operations = op.Operation;
- var operation = operations.Value;
-
- if (!settings.GenerateDeprecatedOperations && operation.IsDeprecated)
- {
- continue;
- }
-
- var returnType = GetTypeName(operation);
- var verb = operations.Key.CapitalizeFirstCharacter();
-
- string interfaceName;
- if (!interfacesByGroup.TryGetValue(kv.Key, out var sb))
- {
- interfacesByGroup[kv.Key] = sb = new StringBuilder();
- this.docGenerator.AppendInterfaceDocumentationByTag(document, kv.Key, sb);
-
- interfaceName = GetInterfaceName(kv.Key);
- sb.AppendLine($$"""
- {{GenerateInterfaceDeclaration(interfaceName)}}
- {{Separator}}{
- """);
-
- interfacesNamesByGroup[kv.Key] = interfaceName;
- }
- else
- {
- interfaceName = interfacesNamesByGroup[kv.Key];
- }
-
- var operationName = GetOperationName(interfaceName, op.PathItem.Key, operations.Key, operation);
- var dynamicQuerystringParameterType = operationName + "QueryParams";
- var operationModel = generator.CreateOperationModel(operation);
- var parameters = ParameterExtractor
- .GetParameters(
- operationModel,
- operation,
- settings,
- dynamicQuerystringParameterType,
- out var operationDynamicQuerystringParameters)
- .ToList();
-
- var hasDynamicQuerystringParameter = !string.IsNullOrWhiteSpace(operationDynamicQuerystringParameters);
- if (hasDynamicQuerystringParameter)
- yield return new GeneratedCode(
- dynamicQuerystringParameterType,
- operationDynamicQuerystringParameters!);
-
- var parametersString = string.Join(", ", parameters);
- var hasApizrRequestOptionsParameter = settings.ApizrSettings?.WithRequestOptions == true;
- var hasCancellationToken = settings.UseCancellationTokens && !hasApizrRequestOptionsParameter;
-
- this.docGenerator.AppendMethodDocumentation(operationModel, IsApiResponseType(returnType), hasDynamicQuerystringParameter, hasApizrRequestOptionsParameter, hasCancellationToken, sb);
- GenerateObsoleteAttribute(operation, sb);
- GenerateForMultipartFormData(operationModel, sb);
- GenerateHeaders(operations, operationModel, sb);
-
- sb.AppendLine($"{Separator}{Separator}[{verb}(\"{op.PathItem.Key}\")]")
- .AppendLine($"{Separator}{Separator}{returnType} {operationName}({parametersString});")
- .AppendLine();
-
- if (parametersString.Contains("?") && settings is { OptionalParameters: true, ApizrSettings: not null })
- {
- this.docGenerator.AppendMethodDocumentation(operationModel, IsApiResponseType(returnType), false, hasApizrRequestOptionsParameter, hasCancellationToken, sb);
- GenerateObsoleteAttribute(operation, sb);
- GenerateForMultipartFormData(operationModel, sb);
- GenerateHeaders(operations, operationModel, sb);
-
- parametersString = string.Join(", ", parameters.Where(parameter => !parameter.Contains("?")));
-
- sb.AppendLine($"{Separator}{Separator}[{verb}(\"{op.PathItem.Key}\")]")
- .AppendLine($"{Separator}{Separator}{returnType} {operationName}({parametersString});")
- .AppendLine();
- }
- }
- }
-
- foreach (var keyValuePair in interfacesByGroup)
- {
- var code = new StringBuilder();
- var key = keyValuePair.Key;
- var value = keyValuePair.Value;
-
- while (char.IsWhiteSpace(value[value.Length - 1]))
- {
- value.Length--;
- }
-
- code.AppendLine(value.ToString());
- code.AppendLine($"{Separator}}}");
-
- yield return new GeneratedCode(
- interfacesNamesByGroup[key],
- code.ToString());
- }
- }
-
- private string GetGroupName(OpenApiOperation operation, string ungroupedTitle)
- {
- if (operation.Tags.FirstOrDefault() is string group && !string.IsNullOrWhiteSpace(group))
- {
- return group.SanitizeControllerTag();
- }
-
- return ungroupedTitle;
- }
-
- private string GetInterfaceName(string name)
- {
- var generatedName = IdentifierUtils.Counted(
- knownIdentifiers,
- $"I{name.CapitalizeFirstCharacter()}",
- suffix: "Api"
- );
-
- knownIdentifiers.Add(generatedName);
- return generatedName;
- }
-
- private string GetOperationName(
- string interfaceName,
- string name,
- string verb,
- OpenApiOperation operation)
- {
- // Initialize per-interface tracking if needed
- if (!_methodIdentifiersByInterface!.ContainsKey(interfaceName))
- {
- _methodIdentifiersByInterface[interfaceName] = new HashSet();
- }
-
- var interfaceIdentifiers = _methodIdentifiersByInterface[interfaceName];
- var generatedName = IdentifierUtils.Counted(
- interfaceIdentifiers,
- GenerateOperationName(name, verb, operation, capitalizeFirstCharacter: true),
- parent: interfaceName);
-
- interfaceIdentifiers.Add(generatedName);
- return generatedName;
- }
-
- private string GenerateInterfaceDeclaration(string name)
- {
- var modifier = settings.TypeAccessibility.ToString().ToLowerInvariant();
- return $"""
- {Separator}{GetGeneratedCodeAttribute()}
- {Separator}{modifier} partial interface {name}
- """;
- }
-}
diff --git a/src/Refitter.Core/RefitMultipleInterfaceGenerator.cs b/src/Refitter.Core/RefitMultipleInterfaceGenerator.cs
deleted file mode 100644
index 80ff3c199..000000000
--- a/src/Refitter.Core/RefitMultipleInterfaceGenerator.cs
+++ /dev/null
@@ -1,113 +0,0 @@
-using System.Text;
-using NSwag;
-
-namespace Refitter.Core;
-
-internal class RefitMultipleInterfaceGenerator : RefitInterfaceGenerator
-{
- private readonly HashSet knownIdentifiers = new();
-
- internal RefitMultipleInterfaceGenerator(
- RefitGeneratorSettings settings,
- OpenApiDocument document,
- CustomCSharpClientGenerator generator,
- XmlDocumentationGenerator docGenerator)
- : base(settings, document, generator, docGenerator)
- {
- }
-
- public override IEnumerable GenerateCode()
- {
- foreach (var kv in document.Paths)
- {
- foreach (var operations in kv.Value)
- {
- var operation = operations.Value;
-
- if (!settings.GenerateDeprecatedOperations && operation.IsDeprecated)
- {
- continue;
- }
-
- var returnType = GetTypeName(operation);
- var verb = operations.Key.CapitalizeFirstCharacter();
- var methodName = !string.IsNullOrWhiteSpace(settings.OperationNameTemplate)
- ? settings.OperationNameTemplate!.Replace("{operationName}", "Execute")
- : "Execute";
- var code = new StringBuilder();
-
- this.docGenerator.AppendInterfaceDocumentationByEndpoint(operation, code);
-
- var interfaceName = GetInterfaceName(kv, verb, operation);
- code.AppendLine($$"""
- {{GenerateInterfaceDeclaration(interfaceName)}}
- {{Separator}}{
- """);
-
- var operationModel = generator.CreateOperationModel(operation);
- var dynamicQuerystringParameterType = interfaceName.Substring(1).Replace("Endpoint", "QueryParams");
- var parameters = ParameterExtractor.GetParameters(operationModel, operation, settings, dynamicQuerystringParameterType, out var methodDynamicQuerystringParameters).ToList();
-
- var hasDynamicQuerystringParameter = !string.IsNullOrWhiteSpace(methodDynamicQuerystringParameters);
- if (hasDynamicQuerystringParameter)
- yield return new GeneratedCode(
- dynamicQuerystringParameterType,
- methodDynamicQuerystringParameters!);
-
- var parametersString = string.Join(", ", parameters);
- var hasApizrRequestOptionsParameter = settings.ApizrSettings?.WithRequestOptions == true;
- var hasCancellationToken = settings.UseCancellationTokens && !hasApizrRequestOptionsParameter;
-
- this.docGenerator.AppendMethodDocumentation(operationModel, IsApiResponseType(returnType), hasDynamicQuerystringParameter, hasApizrRequestOptionsParameter, hasCancellationToken, code);
- GenerateObsoleteAttribute(operation, code);
- GenerateForMultipartFormData(operationModel, code);
- GenerateHeaders(operations, operationModel, code);
-
- code.AppendLine($"{Separator}{Separator}[{verb}(\"{kv.Key}\")]")
- .AppendLine($"{Separator}{Separator}{returnType} {methodName}({parametersString});")
- .AppendLine($"{Separator}}}");
-
- if (parametersString.Contains("?") && settings is { OptionalParameters: true, ApizrSettings: not null })
- {
- this.docGenerator.AppendMethodDocumentation(operationModel, IsApiResponseType(returnType), false, hasApizrRequestOptionsParameter, hasCancellationToken, code);
- GenerateObsoleteAttribute(operation, code);
- GenerateForMultipartFormData(operationModel, code);
- GenerateHeaders(operations, operationModel, code);
-
- parametersString = string.Join(", ", parameters.Where(parameter => !parameter.Contains("?")));
-
- code.AppendLine($"{Separator}{Separator}[{verb}(\"{kv.Key}\")]")
- .AppendLine($"{Separator}{Separator}{returnType} {methodName}({parametersString});")
- .AppendLine();
- }
-
- yield return new GeneratedCode(interfaceName, code.ToString());
- }
- }
- }
-
- private string GetInterfaceName(
- KeyValuePair kv,
- string verb,
- OpenApiOperation operation)
- {
- var name = IdentifierUtils.Counted(
- knownIdentifiers,
- "I" + generator
- .BaseSettings
- .OperationNameGenerator
- .GetOperationName(document, kv.Key, verb, operation).CapitalizeFirstCharacter(),
- suffix: "Endpoint");
- knownIdentifiers.Add(name);
- return name;
- }
-
- private string GenerateInterfaceDeclaration(string name)
- {
- var modifier = settings.TypeAccessibility.ToString().ToLowerInvariant();
- return $"""
- {Separator}{GetGeneratedCodeAttribute()}
- {Separator}{modifier} partial interface {name}
- """;
- }
-}
diff --git a/src/Refitter.Core/SingleInterfacePartitioning.cs b/src/Refitter.Core/SingleInterfacePartitioning.cs
new file mode 100644
index 000000000..b989278a4
--- /dev/null
+++ b/src/Refitter.Core/SingleInterfacePartitioning.cs
@@ -0,0 +1,40 @@
+using System.Text;
+using NSwag;
+
+namespace Refitter.Core;
+
+internal class SingleInterfacePartitioning(
+ RefitGeneratorSettings settings) : IInterfacePartitioning
+{
+
+ public string GetGroupKey(OpenApiOperationInfo operation) => string.Empty;
+
+ public string GetInterfaceName(string groupKey, string title, string baseOperationName) =>
+ $"I{title.CapitalizeFirstCharacter()}".Sanitize();
+
+ public string GetInterfaceNameSuffix() => string.Empty;
+
+ public string GetMethodName(OpenApiOperationInfo operation, string interfaceName, string baseOperationName)
+ {
+ var methodName = baseOperationName;
+ if (settings.OperationNameTemplate?.Contains("{operationName}") ?? false)
+ {
+ methodName = settings.OperationNameTemplate!.Replace("{operationName}", methodName);
+ }
+
+ return methodName;
+ }
+
+ public string GetDynamicQuerystringParameterType(string interfaceName, string methodName) =>
+ methodName + "QueryParams";
+
+ public bool IsSingleInterface => true;
+
+ public void AppendInterfaceDocumentation(
+ OpenApiDocument document,
+ XmlDocumentationGenerator docGenerator,
+ string groupKey,
+ OpenApiOperationInfo representativeOperation,
+ StringBuilder code) =>
+ docGenerator.AppendSingleInterfaceDocumentation(document, code);
+}
diff --git a/src/Refitter.Core/XmlDocumentationGenerator.cs b/src/Refitter.Core/XmlDocumentationGenerator.cs
index a233e594f..2753a1ce3 100644
--- a/src/Refitter.Core/XmlDocumentationGenerator.cs
+++ b/src/Refitter.Core/XmlDocumentationGenerator.cs
@@ -10,19 +10,8 @@ namespace Refitter.Core;
///
public class XmlDocumentationGenerator
{
- ///
- /// The global code generation settings.
- ///
- private readonly RefitGeneratorSettings _settings;
-
- ///
- /// The whitespace to use for a single level of indentation.
- ///
+ private readonly RefitGeneratorSettings settings;
private const string Separator = " ";
-
- ///
- /// The name of the XML documentation tag used for summaries.
- ///
private const string SummaryTag = "summary";
///
@@ -31,7 +20,7 @@ public class XmlDocumentationGenerator
/// The code generation settings to use.
internal XmlDocumentationGenerator(RefitGeneratorSettings settings)
{
- this._settings = settings;
+ this.settings = settings;
}
///
@@ -42,7 +31,7 @@ internal XmlDocumentationGenerator(RefitGeneratorSettings settings)
/// The builder to append the documentation to.
public void AppendInterfaceDocumentationByTag(OpenApiDocument document, string tag, StringBuilder code)
{
- if (!_settings.GenerateXmlDocCodeComments)
+ if (!settings.GenerateXmlDocCodeComments)
{
return;
}
@@ -60,7 +49,7 @@ public void AppendInterfaceDocumentationByTag(OpenApiDocument document, string t
/// The builder to append the documentation to.
public void AppendInterfaceDocumentationByEndpoint(OpenApiOperation endpoint, StringBuilder code)
{
- if (!_settings.GenerateXmlDocCodeComments)
+ if (!settings.GenerateXmlDocCodeComments)
{
return;
}
@@ -80,7 +69,7 @@ public void AppendInterfaceDocumentationByEndpoint(OpenApiOperation endpoint, St
/// The builder to append the documentation to.
public void AppendSingleInterfaceDocumentation(OpenApiDocument document, StringBuilder code)
{
- if (!_settings.GenerateXmlDocCodeComments)
+ if (!settings.GenerateXmlDocCodeComments)
{
return;
}
@@ -109,7 +98,7 @@ public void AppendMethodDocumentation(
bool hasCancellationToken,
StringBuilder code)
{
- if (!_settings.GenerateXmlDocCodeComments)
+ if (!settings.GenerateXmlDocCodeComments)
return;
if (!string.IsNullOrWhiteSpace(method.Summary))
@@ -309,7 +298,7 @@ private string BuildResponseDescription(string text, IEnumerable
{
internal const string GeneratedFileMarker = "GeneratedFile: ";
- private RefitGeneratorSettings? _cachedSettings;
- private IGenerationReporter _reporter = new RichGenerationReporter();
+ private RefitGeneratorSettings? cachedSettings;
+ private IGenerationReporter reporter = new RichGenerationReporter();
private static IGenerationReporter CreateReporter(Settings settings) =>
settings.SimpleOutput
@@ -34,7 +34,7 @@ protected override ValidationResult Validate(CommandContext context, Settings se
var validationResult = SettingsValidator.Validate(settings, out var refitSettings);
if (refitSettings != null)
{
- _cachedSettings = refitSettings;
+ cachedSettings = refitSettings;
}
// Detect conflict: both CLI and settings file specify non-default jsonLibraryVersion
@@ -54,15 +54,15 @@ protected override async Task ExecuteAsync(CommandContext context, Settings
{
RefitGeneratorSettings refitGeneratorSettings;
- _reporter = CreateReporter(settings);
+ reporter = CreateReporter(settings);
try
{
// Use cached settings from Validate() if available
- if (_cachedSettings != null)
+ if (cachedSettings != null)
{
- refitGeneratorSettings = _cachedSettings;
- _cachedSettings = null; // Clear cache after use
+ refitGeneratorSettings = cachedSettings;
+ cachedSettings = null; // Clear cache after use
}
else if (!string.IsNullOrWhiteSpace(settings.SettingsFilePath))
{
@@ -87,14 +87,14 @@ protected override async Task ExecuteAsync(CommandContext context, Settings
version += " (local build)";
// Header with branding
- _reporter.ReportHeader(version);
+ reporter.ReportHeader(version);
// Support information
var supportKey = settings.NoLogging
? "Unavailable when logging is disabled"
: SupportInformation.GetSupportKey();
- _reporter.ReportSupportKey(supportKey);
+ reporter.ReportSupportKey(supportKey);
if (context.Arguments.Any(a => a.Equals("--version", StringComparison.OrdinalIgnoreCase)) ||
context.Arguments.Any(a => a.Equals("-v", StringComparison.OrdinalIgnoreCase)))
@@ -114,13 +114,13 @@ protected override async Task ExecuteAsync(CommandContext context, Settings
if (refitGeneratorSettings.OpenApiPaths == null || refitGeneratorSettings.OpenApiPaths.Length == 0)
{
var specPath = refitGeneratorSettings.OpenApiPath!;
- await ValidateOpenApiSpec(specPath, _reporter);
+ await ValidateOpenApiSpec(specPath, reporter);
}
else
{
foreach (var specPath in refitGeneratorSettings.OpenApiPaths)
{
- await ValidateOpenApiSpec(specPath, _reporter);
+ await ValidateOpenApiSpec(specPath, reporter);
}
}
}
@@ -140,16 +140,16 @@ protected override async Task ExecuteAsync(CommandContext context, Settings
if (refitGeneratorSettings.IncludePathMatches.Length > 0 &&
generator.OpenApiDocument.Paths.Count == 0)
{
- _reporter.ReportAllPathsFilteredWarning(refitGeneratorSettings.IncludePathMatches);
+ reporter.ReportAllPathsFilteredWarning(refitGeneratorSettings.IncludePathMatches);
}
// Success summary with performance metrics
stopwatch.Stop();
- _reporter.ReportSuccess(stopwatch.Elapsed, refitGeneratorSettings.GenerateMultipleFiles);
+ reporter.ReportSuccess(stopwatch.Elapsed, refitGeneratorSettings.GenerateMultipleFiles);
if (!settings.NoBanner)
{
- _reporter.ReportDonationBanner();
+ reporter.ReportDonationBanner();
}
ShowWarnings(refitGeneratorSettings, settings);
@@ -158,24 +158,24 @@ protected override async Task ExecuteAsync(CommandContext context, Settings
catch (Exception exception)
{
// Error summary panel
- _reporter.ReportGenerationFailed();
+ reporter.ReportGenerationFailed();
if (exception is OpenApiUnsupportedSpecVersionException unsupportedSpecVersionException)
{
- _reporter.ReportUnsupportedVersion(unsupportedSpecVersionException.SpecificationVersion);
+ reporter.ReportUnsupportedVersion(unsupportedSpecVersionException.SpecificationVersion);
}
if (exception is not OpenApiValidationException)
{
- _reporter.ReportExceptionDetails(exception);
+ reporter.ReportExceptionDetails(exception);
}
if (!settings.SkipValidation)
{
- _reporter.ReportSkipValidationSuggestion();
+ reporter.ReportSkipValidationSuggestion();
}
- _reporter.ReportSupportHelp();
+ reporter.ReportSupportHelp();
await Analytics.LogError(exception, settings);
return exception.HResult;
@@ -244,7 +244,7 @@ private async Task WriteSingleFile(
Settings settings,
RefitGeneratorSettings refitGeneratorSettings)
{
- await _reporter.ReportSingleFileGenerationProgressAsync();
+ await reporter.ReportSingleFileGenerationProgressAsync();
var code = generator.Generate().ReplaceLineEndings();
var planned = OutputPlanner.PlanSingleFile(settings, refitGeneratorSettings, code);
@@ -254,10 +254,10 @@ private async Task WriteSingleFile(
var sizeFormatted = FormatFileSize(code.Length);
var lines = code.Split('\n').Length;
- _reporter.ReportSingleFileOutput(fileName, directory, sizeFormatted, lines);
+ reporter.ReportSingleFileOutput(fileName, directory, sizeFormatted, lines);
await FileWriter.WriteAsync(planned);
- _reporter.ReportFileWritten(planned.Path);
+ reporter.ReportFileWritten(planned.Path);
}
private static string FormatFileSize(long bytes)
@@ -279,13 +279,13 @@ private async Task WriteMultipleFiles(
Settings settings,
RefitGeneratorSettings refitGeneratorSettings)
{
- var generatorOutput = await _reporter.GenerateMultipleFilesWithProgressAsync(generator.GenerateMultipleFiles);
+ var generatorOutput = await reporter.GenerateMultipleFilesWithProgressAsync(generator.GenerateMultipleFiles);
var planned = OutputPlanner.PlanMultipleFiles(settings, refitGeneratorSettings, generatorOutput);
var totalSize = 0L;
var totalLines = 0;
- var report = _reporter.BeginMultiFileOutput();
+ var report = reporter.BeginMultiFileOutput();
for (var i = 0; i < generatorOutput.Files.Count; i++)
{
@@ -302,7 +302,7 @@ private async Task WriteMultipleFiles(
totalLines += lines;
await FileWriter.WriteAsync(plannedFile);
- _reporter.ReportFileWritten(plannedFile.Path);
+ reporter.ReportFileWritten(plannedFile.Path);
}
report.Complete(generatorOutput.Files.Count, FormatFileSize(totalSize), totalLines);
@@ -333,7 +333,7 @@ private void ShowWarnings(RefitGeneratorSettings refitGeneratorSettings, Setting
if (warnings.Any())
{
- _reporter.ReportConfigurationWarnings(warnings);
+ reporter.ReportConfigurationWarnings(warnings);
}
}
private static string GetOutputPath(Settings settings, RefitGeneratorSettings refitGeneratorSettings) =>
diff --git a/test/OpenAPI/v3.0/bot.components.yaml b/test/OpenAPI/v3.0/bot.components.yaml
deleted file mode 100644
index ef9527418..000000000
--- a/test/OpenAPI/v3.0/bot.components.yaml
+++ /dev/null
@@ -1,818 +0,0 @@
-openapi: 3.0.3
-info:
- title: Bot OpenAPI
- version: v1.0.0
-paths:
- # 这里只是放置个 path 配置避免格式检查报错
-# 对象定义
-components:
- # path 参数统一定义在这里
- parameters:
- PathGuildID:
- name: guild_id
- description: 频道ID
- in: path
- required: true
- schema:
- $ref: '#/components/schemas/BaseGuildID'
- PathChannelID:
- name: channel_id
- description: 子频道ID
- in: path
- required: true
- schema:
- $ref: '#/components/schemas/BaseChannelID'
- PathUserID:
- name: user_id
- description: 成员ID
- in: path
- required: true
- schema:
- $ref: '#/components/schemas/BaseUserID'
- PathRoleID:
- name: role_id
- description: 身份组ID
- in: path
- required: true
- schema:
- $ref: '#/components/schemas/BaseRoleID'
- PathMessageID:
- name: message_id
- description: 消息ID
- in: path
- required: true
- schema:
- $ref: '#/components/schemas/BaseMessageID'
- PathScheduleID:
- name: schedule_id
- description: 日程ID
- in: path
- required: true
- schema:
- $ref: '#/components/schemas/BaseScheduleID'
- schemas:
- BaseGuildID:
- type: string
- format: int64
- description: 频道ID
- BaseChannelID:
- type: string
- format: int64
- description: 子频道ID
- BaseChannelCategoryID:
- type: string
- format: int64
- description: 子频道分组ID,仅子频道支持分组
- BaseUserID:
- type: string
- format: int64
- description: 用户ID
- BaseRoleID:
- type: string
- format: int64
- description: 身份组ID
- BaseMessageID:
- type: string
- description: 消息ID
- BaseSequenceInChannel:
- type: string
- description: |
- 子频道消息 seq,用于消息间的排序,seq 在同一子频道中按从先到后的顺序递增,不同的子频道之间消息无法排序
- BaseScheduleID:
- type: string
- format: int64
- description: 日程ID
- Guild:
- description: |
- 频道对象,频道对象中所涉及的 ID 类数据,都仅在机器人场景流通,与真实的 ID 无关。
- 请不要理解为真实的 ID
- type: object
- properties:
- id:
- $ref: '#/components/schemas/BaseGuildID'
- name:
- type: string
- description: 频道名称
- icon:
- type: string
- description: 频道头像地址
- owner_id:
- $ref: '#/components/schemas/BaseUserID'
- owner:
- type: boolean
- description: 当前人是否是创建人
- member_count:
- type: integer
- description: 成员数
- format: int32
- max_members:
- type: integer
- description: 最大成员数
- format: int32
- description:
- type: string
- description: 描述
- joined_at:
- type: string
- description: 加入时间
- User:
- description: |
- 用户对象中所涉及的 ID 类数据,都仅在机器人场景流通,与真实的 ID 无关。
- 请不要理解为真实的 ID
- type: object
- properties:
- id:
- $ref: '#/components/schemas/BaseUserID'
- username:
- type: string
- description: 用户名
- avatar:
- type: string
- description: 用户头像地址
- bot:
- type: boolean
- description: 是否是机器人
- union_openid:
- type: string
- description: |
- 特殊关联应用的 openid,需要特殊申请并配置后才会返回。如需申请,请联系平台运营人员
- union_user_account:
- type: string
- description: |
- 机器人关联的互联应用的用户信息,与union_openid关联的应用是同一个。
- 如需申请,请联系平台运营人员
- Channel:
- description: 子频道对象,子频道对象中所涉及的 ID 类数据,都仅在机器人场景流通,与真实的 ID 无关。请不要理解为真实的 ID
- type: object
- properties:
- id:
- $ref: '#/components/schemas/BaseChannelID'
- guild_id:
- $ref: '#/components/schemas/BaseGuildID'
- name:
- type: string
- description: 子频道名称
- type:
- $ref: '#/components/schemas/ChannelType'
- sub_type:
- $ref: '#/components/schemas/ChannelSubType'
- position:
- type: integer
- description: 排序值
- parent_id:
- type: string
- description: 所属分组 id,仅对子频道有效,对 子频道分组(ChannelType=4) 无效
- owner_id:
- $ref: '#/components/schemas/BaseUserID'
- private_type:
- $ref: '#/components/schemas/PrivateType'
- speak_permission:
- $ref: '#/components/schemas/SpeakPermission'
- application_id:
- $ref: '#/components/schemas/Application'
- Member:
- description: 成员对象
- type: object
- properties:
- user:
- $ref: '#/components/schemas/User'
- nick:
- type: string
- description: 用户的昵称
- roles:
- type: array
- items:
- $ref: '#/components/schemas/BaseRoleID'
- description: 用户在频道内的身份组ID
- joined_at:
- type: string
- format: date-time
- description: '用户加入频道的时间, ISO8601格式'
- Role:
- type: object
- properties:
- id:
- $ref: '#/components/schemas/BaseRoleID'
- name:
- type: string
- description: 名称
- color:
- type: integer
- description: ARGB的HEX十六进制颜色值转换后的十进制数值
- hoist:
- type: integer
- description: '是否在成员列表中单独展示: 0-否, 1-是'
- number:
- type: integer
- description: 人数
- member_limit:
- type: integer
- description: 成员上限
- format: uint32
- description: 频道身份组对象
- ChannelPermissions:
- description: |
- 子频道权限对象, 权限是QQ频道管理频道成员的一种方式,管理员可以对不同的人、不同的子频道设置特定的权限。
- * 用户的权限包括个人权限和身份组权限两部分,最终生效是取两种权限的并集。
- * 权限使用位图表示,传递时序列化为十进制数值字符串。如权限值为0x6FFF,会被序列化为十进制'28671'
- type: object
- properties:
- channel_id:
- $ref: '#/components/schemas/BaseChannelID'
- user_id:
- $ref: '#/components/schemas/BaseUserID'
- role_id:
- $ref: '#/components/schemas/BaseRoleID'
- permissions:
- $ref: '#/components/schemas/Permissions'
- Message:
- type: object
- description: 消息对象
- properties:
- id:
- $ref: '#/components/schemas/BaseMessageID'
- channel_id:
- $ref: '#/components/schemas/BaseChannelID'
- guild_id:
- $ref: '#/components/schemas/BaseGuildID'
- content:
- type: string
- description: 消息内容
- timestamp:
- type: string
- format: date-time
- description: '消息创建时间,ISO8601 timestamp'
- edited_timestamp:
- type: string
- format: date-time
- description: '消息编辑时间,ISO8601 timestamp'
- mention_everyone:
- type: boolean
- description: 是否是@全员消息
- author:
- $ref: '#/components/schemas/User'
- attachments:
- type: array
- description: 附件
- items:
- $ref: '#/components/schemas/MessageAttachment'
- embeds:
- type: array
- description: embed
- items:
- $ref: '#/components/schemas/MessageEmbed'
- mentions:
- type: array
- description: 消息中@的人
- items:
- $ref: '#/components/schemas/User'
- member:
- $ref: '#/components/schemas/Member'
- ark:
- $ref: '#/components/schemas/MessageArk'
- seq:
- type: integer
- description: |
- 用于消息间的排序,seq 在同一子频道中按从先到后的顺序递增,不同的子频道之前消息无法排序
- seq_in_channel:
- $ref: "#/components/schemas/BaseSequenceInChannel"
- message_reference:
- $ref: "#/components/schemas/MessageReference"
- MessageReference:
- type: object
- description: 引用消息对象
- properties:
- message_id:
- $ref: '#/components/schemas/BaseMessageID'
- ignore_get_message_error:
- type: boolean
- description: 是否忽略获取引用消息详情错误,默认否,仅在发送引用消息的时候使用
- MessageAttachment:
- type: object
- description: 消息附件
- properties:
- url:
- type: string
- description: 下载地址
- MessageEmbed:
- type: object
- description: embed消息
- properties:
- title:
- type: string
- description: 标题
- prompt:
- type: string
- description: 消息弹窗内容
- thumbnail:
- $ref: '#/components/schemas/MessageEmbedThumbnail'
- fields:
- type: array
- description: embed 字段数据
- items:
- $ref: '#/components/schemas/MessageEmbedField'
- MessageEmbedThumbnail:
- type: object
- description: 消息封面
- properties:
- url:
- type: string
- description: 图片地址
- MessageEmbedField:
- type: object
- description: embed字段
- properties:
- name:
- type: string
- description: 字段
- MessageArk:
- type: object
- description: ark消息
- properties:
- template_id:
- type: integer
- description: ark模板id(需要先申请)
- kv:
- type: array
- description: kv值列表
- items:
- $ref: '#/components/schemas/MessageArkKv'
- MessageArkKv:
- description: ARK消息KV
- type: object
- properties:
- key:
- type: string
- description: key
- value:
- type: string
- description: value
- obj:
- type: array
- description: ark obj类型的列表
- items:
- $ref: '#/components/schemas/MessageArkObj'
- MessageArkObj:
- type: object
- description: MessageArkObj
- properties:
- obj_kv:
- type: array
- description: ark objkv列表
- items:
- $ref: '#/components/schemas/MessageArkObjKv'
- MessageArkObjKv:
- type: object
- description: MessageArkObjKv
- properties:
- key:
- type: string
- description: key
- value:
- type: string
- description: value
- MessageAudited:
- type: object
- description: 消息审核对象
- properties:
- audit_id:
- type: string
- description: 消息审核 id
- message_id:
- type: string
- description: 消息 id,只有审核通过事件才会有值
- guild_id:
- type: string
- description: 频道 id
- channel_id:
- type: string
- description: 子频道 id
- audit_time:
- type: string
- format: date-time
- description: '消息审核时间,ISO8601 timestamp'
- create_time:
- type: string
- format: date-time
- description: '消息创建时间,ISO8601 timestamp'
- seq_in_channel:
- $ref: "#/components/schemas/BaseSequenceInChannel"
- DMS:
- type: object
- description: 私信会话对象
- properties:
- guild_id:
- type: string
- description: 私信会话关联的频道ID
- format: int64
- channel_id:
- type: string
- description: 私信会话关联的子频道ID
- create_time:
- type: string
- format: int64
- description: 创建私信会话时间戳
- Announces:
- type: object
- description: 公告对象
- properties:
- guild_id:
- $ref: '#/components/schemas/BaseGuildID'
- channel_id:
- $ref: '#/components/schemas/BaseChannelID'
- message_id:
- $ref: '#/components/schemas/BaseMessageID'
- Schedule:
- type: object
- description: 日程对象
- properties:
- id:
- $ref: '#/components/schemas/BaseScheduleID'
- name:
- type: string
- description: 日程名称
- description:
- type: string
- description: 日程描述
- start_timestamp:
- type: string
- description: 日程开始时间戳(ms)
- format: int64
- end_timestamp:
- type: string
- description: 日程结束时间戳(ms)
- format: int64
- creator:
- $ref: '#/components/schemas/Member'
- jump_channel_id:
- description: 日程开始时跳转到的子频道 id
- type: string
- format: int64
- remind_type:
- $ref: '#/components/schemas/RemindType'
- Emoji:
- type: object
- description: 表情对象
- properties:
- id:
- $ref: '#/components/schemas/EmojiID'
- type:
- $ref: '#/components/schemas/EmojiType'
- MessageReaction:
- type: object
- description: 表情表态对象
- properties:
- user_id:
- $ref: '#/components/schemas/BaseUserID'
- guild_id:
- $ref: '#/components/schemas/BaseGuildID'
- channel_id:
- $ref: '#/components/schemas/BaseChannelID'
- target:
- $ref: '#/components/schemas/ReactionTarget'
- emoji:
- $ref: '#/components/schemas/Emoji'
- ReactionTarget:
- type: object
- description: 表情表态的目标对象
- properties:
- id:
- type: string
- description: 表态对象ID
- type:
- $ref: '#/components/schemas/ReactionTargetType'
- AudioControl:
- type: object
- description: 语音对象
- properties:
- audio_url:
- type: string
- description: 音频数据的url status为0时传
- text:
- type: string
- description: 状态文本(比如:简单爱-周杰伦),可选,status为0时传,其他操作不传
- status:
- $ref: '#/components/schemas/AudioControlStatus'
- APIPermission:
- type: object
- description: 接口权限对象
- properties:
- path:
- type: string
- description: 'API 接口名,例如 /guilds/{guild_id}/members/{user_id}'
- method:
- type: string
- description: 请求方法,例如 GET
- desc:
- type: string
- description: API 接口名称,例如 获取频道信
- auth_status:
- type: integer
- description: 授权状态,auth_stats 为 1 时已授权
- APIPermissionDemand:
- type: object
- description: 接口权限需求对象,用于往目标频道的子频道发送申请接口权限消息
- properties:
- guild_id:
- $ref: '#/components/schemas/BaseGuildID'
- channel_id:
- $ref: '#/components/schemas/BaseChannelID'
- api_identify:
- $ref: '#/components/schemas/APIPermissionDemandIdentify'
- title:
- type: string
- description: 接口权限链接中的接口权限描述信息
- desc:
- type: string
- description: 接口权限链接中的机器人可使用功能的描述信息
- APIPermissionDemandIdentify:
- type: object
- description: 接口权限需求标识对象
- properties:
- path:
- type: string
- description: 'API 接口名,例如 /guilds/{guild_id}/members/{user_id}'
- name:
- type: string
- description: 请求方法,例如 GET
- ChannelCreate:
- type: object
- description: 创建子频道请求对象
- properties:
- name:
- type: string
- description: 子频道名称
- type:
- $ref: '#/components/schemas/ChannelType'
- sub_type:
- $ref: '#/components/schemas/ChannelSubType'
- position:
- type: integer
- description: 排序值
- parent_id:
- $ref: '#/components/schemas/BaseChannelCategoryID'
- private_type:
- $ref: '#/components/schemas/PrivateType'
- private_user_ids:
- type: array
- items:
- $ref: '#/components/schemas/BaseUserID'
- description: 子频道私密类型成员 ID
- required:
- - name
- - type
- - sub_type
- ChannelUpdate:
- type: object
- description: 修子频道请求对象
- properties:
- name:
- type: string
- description: 子频道名称
- type:
- $ref: '#/components/schemas/ChannelType'
- sub_type:
- $ref: '#/components/schemas/ChannelSubType'
- position:
- type: integer
- description: 排序值
- parent_id:
- $ref: '#/components/schemas/BaseChannelCategoryID'
- private_type:
- $ref: '#/components/schemas/PrivateType'
- GuildRole:
- type: object
- description: 频道身份组对象
- properties:
- id:
- $ref: '#/components/schemas/BaseGuildID'
- name:
- type: string
- description: 名称
- color:
- type: number
- description: ARGB 的 HEX 十六进制颜色值转换后的十进制数值(例:4294927682)
- hoist:
- type: number
- description: 是否在成员列表中单独展示, 0-否, 1-是
- number:
- type: number
- description: 人数
- member_limit:
- type: number
- description: 成员上限
- MessageSend:
- type: object
- description: 用户发送消息的数据对象
- properties:
- content:
- type: string
- description: 消息内容,文本内容,支持内嵌格式
- embed:
- $ref: "#/components/schemas/MessageEmbed"
- ark:
- $ref: "#/components/schemas/MessageArk"
- message_reference:
- $ref: "#/components/schemas/MessageReference"
- image:
- type: string
- description: 图片url地址,平台会转存该图片,用于下发图片消息
- msg_id:
- type: string
- description: 要回复的消息id(Message.id), 在 AT_CREATE_MESSAGE 事件中获取,携带之后此条回复视为被动消息
- minProperties: 1
- ScheduleCreate:
- type: object
- description: 日程创建对象
- properties:
- name:
- type: string
- description: 日程名称
- description:
- type: string
- description: 日程描述
- start_timestamp:
- type: string
- description: 日程开始时间戳(ms)
- format: int64
- end_timestamp:
- type: string
- description: 日程结束时间戳(ms)
- format: int64
- creator:
- $ref: '#/components/schemas/Member'
- jump_channel_id:
- type: string
- description: 日程开始时跳转到的子频道 id
- format: int64
- remind_type:
- type: string
- description: 日程提醒类型
- required:
- - name
- - start_timestamp
- - end_timestamp
- - remind_type
- ScheduleUpdate:
- type: object
- description: 日程更新对象
- properties:
- name:
- type: string
- description: 日程名称
- description:
- type: string
- description: 日程描述
- start_timestamp:
- type: string
- description: 日程开始时间戳(ms)
- format: int64
- end_timestamp:
- type: string
- description: 日程结束时间戳(ms)
- format: int64
- creator:
- $ref: '#/components/schemas/Member'
- jump_channel_id:
- type: string
- description: 日程开始时跳转到的子频道 id
- format: int64
- remind_type:
- type: string
- description: 日程提醒类型
- SessionStartLimit:
- type: object
- description: 创建 Session 限制信息
- properties:
- total:
- type: integer
- description: 每 24 小时可创建 Session 数
- remaining:
- type: integer
- description: 目前还可以创建的 Session 数
- reset_after:
- type: integer
- description: 重置计数的剩余时间(ms)
- max_concurrency:
- type: integer
- description: 每 5s 可以创建的 Session 数
- DefaultRoleIDs:
- type: string
- description: |
- 统默认生成下列身份组ID:
- * `1` - 全体成员
- * `2` - 管理员
- * `4` - 群主/创建者
- * `5` - 子频道管理员
- enum: [1,2,4,5]
- ChannelType:
- type: integer
- description: |
- 子频道类型:
- * `0` - 文字子频道
- * `1` - 保留,不可用
- * `2` - 语音子频道
- * `3` - 保留,不可用
- * `4` - 子频道分组
- * `10005` - 直播子频道
- * `10006` - 应用子频道
- * `10007` - 论坛子频道
- | 注:由于QQ频道还在持续的迭代中,经常会有新的子频道类型增加,文档更新不一定及时,开发者识别 ChannelType 时,请注意相关的未知 ID 的处理。
- enum: [0,1,2,3,4,10005,10006,10007]
- ChannelSubType:
- type: integer
- description: |
- 子频道子类型:
- * `0` - 闲聊
- * `1` - 公告
- * `2` - 攻略
- * `3` - 开黑
- | 目前只有文字子频道具有 ChannelSubType 二级分类,同时二级分类也可能会不断增加,开发者也需要注意对未知 ID 的处理
- enum: [0,1,2,3]
- PrivateType:
- type: integer
- description: |
- 子频道私密类型:
- * `0` - 公开频道
- * `1` - 群主管理员可见
- * `2` - 群主管理员+指定成员,可使用 修改子频道权限接口 指定成员
- enum: [0,1,2]
- SpeakPermission:
- type: integer
- description: |
- 子频道发言权限:
- * `0` - 无效类型
- * `1` - 所有人
- * `2` - 群主管理员+指定成员,可使用 修改子频道权限接口 指定成员
- enum: [0,1,2]
- Application:
- type: string
- description: |
- 应用子频道的应用类型:
- * `1000000` - 王者开黑大厅
- * `1000001` - 互动小游戏
- * `1000010` - 腾讯投票
- * `1000051` - 飞车开黑大厅
- * `1000050` - 日程提醒
- * `1000070` - CoDM 开黑大厅
- * `1010000` - 和平精英开黑大厅
- | 由于QQ频道还在持续的迭代中,应用子频道的 application_id 还会持续新增,后续会不定期补充到文档中
- Permissions:
- type: string
- description: |
- * 用户/角色拥有的(子)频道权限
- * 权限是QQ频道管理频道成员的一种方式,管理员可以对不同的人、不同的子频道设置特定的权限。用户的权限包括个人权限和身份组权限两部分,最终生效是取两种权限的并集。
- * 权限使用位图表示,传递时序列化为十进制数值字符串。如权限值为0x6FFF,会被序列化为十进制"28671"。
- | 权限 | 值 | 描述 |
- | --- | --- | --- |
- | 可查看子频道 | 0x0000000001 (1 << 0) | 支持`指定成员`可见类型,支持`身份组`可见类型 |
- | 可管理子频道 | 0x0000000002 (1 << 1) | 创建者、管理员、子频道管理员都具有此权限 |
- | 可发言子频道 | 0x0000000004 (1 << 2) | 支持`指定成员`发言类型,支持`身份组`发言类型 |
- RemindType:
- type: string
- description: |
- 日程提醒类型:
- * `0` - 不提醒
- * `1` - 开始时提醒
- * `2` - 开始前 5 分钟提醒
- * `3` - 开始前 15 分钟提醒
- * `4` - 开始前 30 分钟提醒
- * `5` - 开始前 60 分钟提醒
- AudioControlStatus:
- type: integer
- description: |
- 播放状态:
- * `0` - 开始播放操作
- * `1` - 暂停播放操作
- * `2` - 继续播放操作
- * `3` - 停止播放操作
- ReactionTargetType:
- type: string
- description: |
- 表态对象类型:
- * `0` - 消息
- * `1` - 帖子
- * `2` - 评论
- * `3` - 回复
- EmojiType:
- type: integer
- description: |
- 表情类型 :
- * `1` - 系统表情
- * `2` - emoji表情
- EmojiID:
- type: string
- description: |
- emoji 表情列表,请访问 [表情列表](https://bot.q.qq.com/wiki/develop/api/openapi/emoji/model.html#emoji-%E5%88%97%E8%A1%A8)
- PinsMessage:
- type: object
- description: 精华消息对象
- properties:
- guild_id:
- $ref: '#/components/schemas/BaseGuildID'
- channel_id:
- $ref: '#/components/schemas/BaseChannelID'
- message_ids:
- type: array
- items:
- $ref: '#/components/schemas/BaseMessageID'
diff --git a/test/OpenAPI/v3.0/bot.paths.yaml b/test/OpenAPI/v3.0/bot.paths.yaml
deleted file mode 100644
index c0c47c86f..000000000
--- a/test/OpenAPI/v3.0/bot.paths.yaml
+++ /dev/null
@@ -1,1157 +0,0 @@
-openapi: 3.0.3
-info:
- title: Bot OpenAPI
- description: |
- QQ 频道机器人通过开放的平台承载机器人的定制化功能,让开发者获得更畅快的开发体验。
- version: v1.0.0
- contact:
- name: 加入开发者频道
- url: https://qun.qq.com/qqweb/qunpro/share?_wv=3&_wwv=128&inviteCode=1MVRbV&from=246610&biz=ka
-servers:
- - url: https://sandbox.api.sgroup.qq.com
- description: |
- 沙箱环境
- 沙箱环境调用 openapi 仅能操作沙箱频道
- - url: https://api.sgroup.qq.com
- description: 正式环境
-security:
- - bot_auth: []
-paths:
- /guilds/{guild_id}:
- parameters:
- - $ref: 'bot.components.yaml#/components/parameters/PathGuildID'
- get:
- summary: 获取频道详情
- description: 用于获取 guild_id 指定的频道的详情
- externalDocs:
- url: https://bot.q.qq.com/wiki/develop/api/openapi/guild/get_guild.html
- responses:
- 200:
- description: 成功
- content:
- application/json:
- schema:
- $ref: 'bot.components.yaml#/components/schemas/Guild'
- tags:
- - GuildManagements
- /users/@me:
- get:
- summary: 获取用户详情
- description: 用于获取当前用户(机器人)详情
- externalDocs:
- url: https://bot.q.qq.com/wiki/develop/api/openapi/user/me.html
- responses:
- 200:
- description: 成功
- content:
- application/json:
- schema:
- $ref: 'bot.components.yaml#/components/schemas/User'
- tags:
- - UserRelations
- /users/@me/guilds:
- parameters:
- - name: before
- in: query
- required: false
- schema:
- type: string
- description: 读此 guild id 之前的数据,before 设置时, 先反序,再分页
- - name: after
- in: query
- required: false
- schema:
- type: string
- description: 读取此 id 之后的数据
- - name: limit
- in: query
- required: false
- schema:
- type: number
- default: 100
- description: 每次拉取多少条数据,最大不超过 100,默认 100
- get:
- summary: 获取用户频道列表
- description: |
- 用于获取当前用户(机器人)所加入的频道列表,支持分页,参数 before、after 同时存在时,以 before 为准。
- externalDocs:
- url: 'https://bot.q.qq.com/wiki/develop/api/openapi/user/guilds.html'
- responses:
- 200:
- description: 成功
- content:
- application/json:
- schema:
- type: array
- items:
- $ref: 'bot.components.yaml#/components/schemas/Guild'
- tags:
- - UserRelations
- /guilds/{guild_id}/channels:
- parameters:
- - $ref: 'bot.components.yaml#/components/parameters/PathGuildID'
- get:
- summary: 获取子频道列表
- description: 用于获取 guild_id 指定的频道下的子频道列表
- externalDocs:
- url: https://bot.q.qq.com/wiki/develop/api/openapi/channel/get_channels.html
- responses:
- 200:
- description: 成功
- content:
- application/json:
- schema:
- type: array
- items:
- $ref: 'bot.components.yaml#/components/schemas/Channel'
- tags:
- - GuildManagements
- post:
- summary: 创建子频道
- description: |
- 用于在 guild_id 指定的频道下创建一个子频道。
- * 要求操作人具有管理频道的权限,如果是机器人,则需要将机器人设置为管理员。
- * 创建成功后,返回创建成功的子频道对象,同时会触发一个频道创建的事件通知。
- * `公域机器人暂不支持申请,仅私域机器人可用,选择私域机器人后默认开通。`
- * `注意: 开通后需要先将机器人从频道移除,然后重新添加,方可生效。`
- externalDocs:
- url: https://bot.q.qq.com/wiki/develop/api/openapi/channel/post_channels.html
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: 'bot.components.yaml#/components/schemas/ChannelCreate'
- responses:
- 200:
- description: 成功
- content:
- application/json:
- schema:
- type: array
- items:
- $ref: 'bot.components.yaml#/components/schemas/Channel'
- tags:
- - GuildManagements
- /channels/{channel_id}:
- parameters:
- - $ref: 'bot.components.yaml#/components/parameters/PathChannelID'
- get:
- summary: 获取子频道详情
- description: 用于获取 channel_id 指定的子频道的详情
- externalDocs:
- url: https://bot.q.qq.com/wiki/develop/api/openapi/channel/get_channel.html
- responses:
- 200:
- description: 成功
- content:
- application/json:
- schema:
- $ref: 'bot.components.yaml#/components/schemas/Channel'
- tags:
- - GuildManagements
- patch:
- summary: 修改子频道
- description: |
- 用于修改 channel_id 指定的子频道的信息。
- * 要求操作人具有管理子频道的权限,如果是机器人,则需要将机器人设置为管理员。
- * 修改成功后,会触发子频道更新事件。
- * `公域机器人暂不支持申请,仅私域机器人可用,选择私域机器人后默认开通。`
- * `注意: 开通后需要先将机器人从频道移除,然后重新添加,方可生效。`
- externalDocs:
- url: https://bot.q.qq.com/wiki/develop/api/openapi/channel/patch_channel.html
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: 'bot.components.yaml#/components/schemas/ChannelUpdate'
- responses:
- 200:
- description: 成功
- content:
- application/json:
- schema:
- $ref: 'bot.components.yaml#/components/schemas/Channel'
- tags:
- - GuildManagements
- delete:
- summary: 删除子频道
- description: |
- 用于删除 channel_id 指定的子频道。
- * 要求操作人具有管理子频道的权限,如果是机器人,则需要将机器人设置为管理员。
- * 修改成功后,会触发子频道删除事件。
- * `公域机器人暂不支持申请,仅私域机器人可用,选择私域机器人后默认开通。`
- * `注意: 开通后需要先将机器人从频道移除,然后重新添加,方可生效。`
- externalDocs:
- url: 'https://bot.q.qq.com/wiki/develop/api/openapi/channel/delete_channel.html'
- responses:
- 200:
- description: 成功
- tags:
- - GuildManagements
- /guilds/{guild_id}/members:
- get:
- summary: 获取频道成员列表
- description: |
- 用于获取 guild_id 指定的频道中所有成员的详情列表,支持分页。
- * `公域机器人暂不支持申请,仅私域机器人可用,选择私域机器人后默认开通。`
- * `注意: 开通后需要先将机器人从频道移除,然后重新添加,方可生效。`
- externalDocs:
- url: https://bot.q.qq.com/wiki/develop/api/openapi/member/get_members.html
- parameters:
- - $ref: 'bot.components.yaml#/components/parameters/PathGuildID'
- - name: after
- in: query
- required: false
- schema:
- type: string
- description: 上一次回包中最后一个member的user id, 如果是第一次请求填 0,默认为 0
- - name: limit
- in: query
- required: false
- schema:
- type: number
- default: 1
- description: 分页大小,1-400,默认是 1。成员较多的频道尽量使用较大的limit值,以减少请求数
- responses:
- 200:
- description: 成功
- content:
- application/json:
- schema:
- type: array
- items:
- $ref: 'bot.components.yaml#/components/schemas/Member'
- tags:
- - UserRelations
- /guilds/{guild_id}/members/{user_id}:
- parameters:
- - $ref: 'bot.components.yaml#/components/parameters/PathGuildID'
- - $ref: 'bot.components.yaml#/components/parameters/PathUserID'
- get:
- summary: 获取成员详情
- description: 用于获取 guild_id 指定的频道中 user_id 对应成员的详细信息
- externalDocs:
- url: https://bot.q.qq.com/wiki/develop/api/openapi/member/get_member.html
- responses:
- 200:
- description: 成功
- content:
- application/json:
- schema:
- $ref: 'bot.components.yaml#/components/schemas/Member'
- tags:
- - UserRelations
- delete:
- summary: 删除频道成员
- description: |
- 用于删除 guild_id 指定的频道下的成员 user_id。
- * 需要使用的 token 对应的用户具备踢人权限。如果是机器人,要求被添加为管理员。
- * 操作成功后,会触发频道成员删除事件。
- * 无法移除身份为管理员的成员
- * `公域机器人暂不支持申请,仅私域机器人可用,选择私域机器人后默认开通。`
- * `注意: 开通后需要先将机器人从频道移除,然后重新添加,方可生效。`
- externalDocs:
- url: https://bot.q.qq.com/wiki/develop/api/openapi/member/delete_member.html
- requestBody:
- required: false
- content:
- application/json:
- schema:
- properties:
- add_blacklist:
- type: boolean
- description: 删除成员的同时,将该用户添加到频道黑名单中
- responses:
- 204:
- description: 成功
- tags:
- - UserRelations
- /guilds/{guild_id}/roles:
- parameters:
- - $ref: 'bot.components.yaml#/components/parameters/PathGuildID'
- get:
- summary: 获取频道身份组列表
- description: 用于获取 guild_id指定的频道下的身份组列表
- externalDocs:
- url: https://bot.q.qq.com/wiki/develop/api/openapi/guild/get_guild_roles.html
- responses:
- 200:
- description: 成功
- content:
- application/json:
- schema:
- type: object
- properties:
- guild_id:
- type: string
- description: 频道 ID
- roles:
- type: array
- items:
- $ref: 'bot.components.yaml#/components/schemas/Role'
- description: 频道身份组对象列表
- role_num_limit:
- type: string
- description: 默认分组上限
- tags: [UserRelations]
- post:
- summary: 创建频道身份组
- description: |
- 用于在guild_id 指定的频道下创建一个身份组。
- * 需要使用的 token 对应的用户具备创建身份组权限。如果是机器人,要求被添加为管理员。
- * 参数为非必填,但至少需要传其中之一,默认为空或 0。
- externalDocs:
- url: https://bot.q.qq.com/wiki/develop/api/openapi/guild/post_guild_role.html
- requestBody:
- required: true
- content:
- application/json:
- schema:
- properties:
- name:
- type: string
- description: 名称
- color:
- type: number
- description: ARGB 的 HEX 十六进制颜色值转换后的十进制数值(例:4294927682)
- hoist:
- type: number
- description: 在成员列表中单独展示,0-否, 1-是
- required:
- - name
- responses:
- 200:
- description: 成功
- content:
- application/json:
- schema:
- type: object
- properties:
- role_id:
- type: string
- description: 频道身份组 ID
- role:
- $ref: 'bot.components.yaml#/components/schemas/GuildRole'
- tags: [UserRelations]
-
- /guilds/{guild_id}/roles/{role_id}:
- parameters:
- - $ref: 'bot.components.yaml#/components/parameters/PathGuildID'
- - $ref: 'bot.components.yaml#/components/parameters/PathRoleID'
- patch:
- summary: 修改频道身份组
- description: |
- 用于修改频道 guild_id 下 role_id 指定的身份组。
- * 需要使用的 token 对应的用户具备修改身份组权限。如果是机器人,要求被添加为管理员。
- * 接口会修改传入的字段,不传入的默认不会修改,至少要传入一个参数。
- externalDocs:
- url: https://bot.q.qq.com/wiki/develop/api/openapi/guild/patch_guild_role.html
- requestBody:
- required: true
- content:
- application/json:
- schema:
- properties:
- name:
- type: string
- description: 名称
- color:
- type: number
- description: ARGB 的 HEX 十六进制颜色值转换后的十进制数值(例:4294927682)
- hoist:
- type: number
- description: 在成员列表中单独展示,0-否, 1-是
- responses:
- 200:
- description: 成功
- content:
- application/json:
- schema:
- type: object
- properties:
- guild_id:
- type: string
- description: 频道 ID
- role_id:
- type: string
- description: 频道身份组 ID
- role:
- $ref: 'bot.components.yaml#/components/schemas/GuildRole'
- tags: [UserRelations]
- delete:
- summary: 删除频道身份组
- description: |
- 用于删除频道guild_id下 role_id 对应的身份组。
- * 需要使用的 token 对应的用户具备`删除身份组权限`。如果是机器人,要求被添加为管理员。
- externalDocs:
- url: https://bot.q.qq.com/wiki/develop/api/openapi/guild/delete_guild_role.html
- responses:
- 204:
- description: 成功
- tags: [UserRelations]
-
- /guilds/{guild_id}/members/{user_id}/roles/{role_id}:
- parameters:
- - $ref: 'bot.components.yaml#/components/parameters/PathGuildID'
- - $ref: 'bot.components.yaml#/components/parameters/PathRoleID'
- - $ref: 'bot.components.yaml#/components/parameters/PathUserID'
- put:
- summary: 创建频道身份组成员
- description: |
- 用于将频道guild_id下的用户 user_id 添加到身份组 role_id 。
- * 需要使用的 token 对应的用户具备增加身份组成员权限。如果是机器人,要求被添加为管理员。
- * 如果要增加的身份组 ID 是5-子频道管理员,需要增加 channel 对象来指定具体是哪个子频道。
- externalDocs:
- url: 'https://bot.q.qq.com/wiki/develop/api/openapi/guild/put_guild_member_role.html'
- requestBody:
- required: true
- content:
- application/json:
- schema:
- properties:
- id:
- type: string
- description: 子频道 id
- responses:
- 204:
- description: 成功
- tags: [UserRelations]
- delete:
- summary: 删除频道身份组成员
- description: |
- 用于将 用户 user_id 从 频道 guild_id 的 role_id 身份组中移除。
- * 需要使用的 token 对应的用户具备删除身份组成员权限。如果是机器人,要求被添加为管理员。
- * 如果要删除的身份组 ID 是5-子频道管理员,需要增加 channel 对象来指定具体是哪个子频道。
- externalDocs:
- url: https://bot.q.qq.com/wiki/develop/api/openapi/guild/delete_guild_member_role.html
- requestBody:
- required: true
- content:
- application/json:
- schema:
- properties:
- id:
- type: string
- description: 子频道 id
- responses:
- 204:
- description: 成功
- tags: [UserRelations]
-
- /channels/{channel_id}/members/{user_id}/permissions:
- parameters:
- - $ref: 'bot.components.yaml#/components/parameters/PathChannelID'
- - $ref: 'bot.components.yaml#/components/parameters/PathUserID'
- get:
- summary: 获取子频道用户权限
- description: |
- 用于获取 子频道channel_id 下用户 user_id 的权限。
- * 获取子频道用户权限。
- * 要求操作人具有`管理子频道的权限`,如果是机器人,则需要将机器人设置为管理员。
- externalDocs:
- url: 'https://bot.q.qq.com/wiki/develop/api/openapi/channel_permissions/get_channel_permissions.html'
- responses:
- 200:
- description: 成功
- content:
- application/json:
- schema:
- $ref: 'bot.components.yaml#/components/schemas/ChannelPermissions'
- tags:
- - Permissions
- put:
- summary: 修改子频道权限
- description: |
- 用于修改子频道 channel_id 下用户 user_id 的权限。
- * 要求操作人具有管理子频道的权限,如果是机器人,则需要将机器人设置为管理员。
- * 参数包括add和remove两个字段,分别表示授予的权限以及删除的权限。要授予用户权限即把add对应位置 1,删除用户权限即把remove对应位置 1。当两个字段同一位都为 1,表现为删除权限。
- * `本接口不支持修改可管理子频道权限。`
- externalDocs:
- url: https://bot.q.qq.com/wiki/develop/api/openapi/channel_permissions/put_channel_permissions.html
- requestBody:
- required: true
- content:
- application/json:
- schema:
- properties:
- add:
- type: string
- description: 字符串形式的位图表示赋予用户的权限
- remove:
- type: string
- description: 字符串形式的位图表示删除用户的权限
- responses:
- 204:
- description: 成功
- tags:
- - Permissions
- /channels/{channel_id}/roles/{role_id}/permissions:
- parameters:
- - $ref: 'bot.components.yaml#/components/parameters/PathChannelID'
- - $ref: 'bot.components.yaml#/components/parameters/PathRoleID'
- get:
- summary: 获取子频道身份组权限
- description: |
- 用于获取子频道 channel_id 下身份组 role_id 的权限。
- * 要求操作人具有管理子频道的权限,如果是机器人,则需要将机器人设置为管理员。
- externalDocs:
- url: https://bot.q.qq.com/wiki/develop/api/openapi/channel_permissions/get_channel_roles_permissions.html
- responses:
- 200:
- description: 成功
- content:
- application/json:
- schema:
- $ref: 'bot.components.yaml#/components/schemas/ChannelPermissions'
- tags:
- - Permissions
- put:
- summary: 修改子频道身份组权限
- description: |
- 用于修改子频道 channel_id 下身份组 role_id 的权限。
- * 要求操作人具有管理子频道的权限,如果是机器人,则需要将机器人设置为管理员。
- * 参数包括add和remove两个字段,分别表示授予的权限以及删除的权限。要授予身份组权限即把add对应位置 1,删除身份组权限即把remove对应位置 1。当两个字段同一位都为 1,表现为删除权限。
- * `本接口不支持修改可管理子频道权限。`
- externalDocs:
- url: https://bot.q.qq.com/wiki/develop/api/openapi/channel_permissions/put_channel_roles_permissions.html
- requestBody:
- required: true
- content:
- application/json:
- schema:
- properties:
- add:
- type: string
- description: 字符串形式的位图表示赋予用户的权限
- remove:
- type: string
- description: 字符串形式的位图表示删除用户的权限
- responses:
- 204:
- description: 成功
- tags:
- - Permissions
- /channels/{channel_id}/messages/{message_id}:
- parameters:
- - $ref: 'bot.components.yaml#/components/parameters/PathChannelID'
- - $ref: 'bot.components.yaml#/components/parameters/PathMessageID'
- get:
- summary: 获取指定消息
- description: 用于获取子频道 channel_id 下的消息 message_id 的详情。
- externalDocs:
- url: https://bot.q.qq.com/wiki/develop/api/openapi/message/get_message_of_id.html
- responses:
- 200:
- description: 成功
- content:
- application/json:
- schema:
- $ref: 'bot.components.yaml#/components/schemas/Message'
- tags:
- - Messages
- /channels/{channel_id}/messages:
- parameters:
- - $ref: 'bot.components.yaml#/components/parameters/PathChannelID'
- post:
- summary: 发送消息
- description: |
- 用于向 channel_id 指定的子频道发送消息。
- * 要求操作人在该子频道具有发送消息的权限。
- * 主动推送消息,默认每天往每个频道可推送的消息数是 `20` 条,超过会被限制。
- * 主动推送消息在每个频道中,每天可以往 `2` 个子频道推送消息。超过后会被限制。
- * 不论主动消息还是被动消息,在一个子频道中,每 `1s` 只能发送 `5` 条消息。
- * 被动回复消息有效期为 `5` 分钟。超时会报错。
- * 发送消息接口要求机器人接口需要连接到 websocket 上保持在线状态
- * 有关主动消息审核,可以通过 Intents 中审核事件 MESSAGE_AUDIT 返回 MessageAudited 对象获取结果。
- externalDocs:
- url: https://bot.q.qq.com/wiki/develop/api/openapi/message/post_messages.html
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: 'bot.components.yaml#/components/schemas/MessageSend'
- responses:
- 200:
- description: 成功
- content:
- application/json:
- schema:
- $ref: 'bot.components.yaml#/components/schemas/Message'
- tags:
- - Messages
- /users/@me/dms:
- post:
- summary: 创建私信会话
- description: |
- 用于机器人和在同一个频道内的成员创建私信会话。
- * 机器人和用户存在共同频道才能创建私信会话。
- * 创建成功后,返回创建成功的频道 id ,子频道 id 和创建时间。
- externalDocs:
- url: https://bot.q.qq.com/wiki/develop/api/openapi/dms/post_dms.html
- requestBody:
- required: true
- content:
- application/json:
- schema:
- properties:
- recipient_id:
- type: string
- description: 接收者 id
- source_guild_id:
- type: string
- description: 源频道 id
- required:
- - recipient_id
- - source_guild_id
- responses:
- 200:
- description: 成功
- content:
- application/json:
- schema:
- type: array
- items:
- $ref: 'bot.components.yaml#/components/schemas/DMS'
- tags:
- - Messages
- /dms/{guild_id}/messages:
- parameters:
- - $ref: 'bot.components.yaml#/components/parameters/PathGuildID'
- post:
- summary: 发送私信
- description: |
- 用于发送私信消息,前提是已经创建了私信会话。
- * 私信的 guild_id 在创建私信会话时以及私信消息事件中获取。
- * 私信场景下,每个机器人每天可以对一个用户发 `2` 条主动消息。
- * 私信场景下,每个机器人每天累计可以发 `200` 条主动消息。
- * 私信场景下,被动消息没有条数限制。
- externalDocs:
- url: https://bot.q.qq.com/wiki/develop/api/openapi/dms/post_dms_messages.html
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: 'bot.components.yaml#/components/schemas/MessageSend'
- responses:
- 200:
- description: 成功
- content:
- application/json:
- schema:
- type: array
- items:
- $ref: 'bot.components.yaml#/components/schemas/Message'
- tags:
- - Messages
- /guilds/{guild_id}/mute:
- parameters:
- - $ref: 'bot.components.yaml#/components/parameters/PathGuildID'
- patch:
- summary: 禁言全员
- description: |
- 用于将频道的全体成员(非管理员)禁言。
- * 需要使用的 token 对应的用户具备管理员权限。如果是机器人,要求被添加为管理员。
- * 该接口同样可用于解除禁言,具体使用见解除全员禁言。
- * `该接口同样支持解除全员禁言,将mute_end_timestamp或mute_seconds传值为字符串'0'即可`。
- externalDocs:
- url: 'https://bot.q.qq.com/wiki/develop/api/openapi/guild/patch_guild_mute.html'
- requestBody:
- required: true
- content:
- application/json:
- schema:
- properties:
- mute_end_timestamp:
- type: string
- description: 禁言到期时间戳,绝对时间戳,单位:秒(与 mute_seconds 字段同时赋值的话,以该字段为准)
- mute_seconds:
- type: string
- description: 禁言多少秒(两个字段二选一,默认以 mute_end_timestamp 为准)
- minProperties: 1
- responses:
- 204:
- description: 成功
- tags:
- - Permissions
- /guilds/{guild_id}/members/{user_id}/mute:
- parameters:
- - $ref: 'bot.components.yaml#/components/parameters/PathGuildID'
- - $ref: 'bot.components.yaml#/components/parameters/PathUserID'
- patch:
- summary: 禁言指定成员
- description: |
- 用于禁言频道 guild_id 下的成员 user_id。
- * 需要使用的 token 对应的用户具备管理员权限。如果是机器人,要求被添加为管理员。
- * `该接口同样可用于解除禁言,具体使用见解除指定成员禁言`。
- externalDocs:
- url: https://bot.q.qq.com/wiki/develop/api/openapi/guild/patch_guild_member_mute.html
- requestBody:
- required: true
- content:
- application/json:
- schema:
- properties:
- mute_end_timestamp:
- type: string
- description: 禁言到期时间戳,绝对时间戳,单位:秒(与 mute_seconds 字段同时赋值的话,以该字段为准)
- mute_seconds:
- type: string
- description: 禁言多少秒(两个字段二选一,默认以 mute_end_timestamp 为准)
- minProperties: 1
- responses:
- 204:
- description: 成功
- tags:
- - Permissions
- /guilds/{guild_id}/announces:
- parameters:
- - $ref: 'bot.components.yaml#/components/parameters/PathGuildID'
- post:
- summary: 创建频道公告
- description: |
- 用于将频道内的某条消息设置为频道全局公告
- externalDocs:
- url: https://bot.q.qq.com/wiki/develop/api/openapi/announces/post_guild_announces.html
- requestBody:
- required: true
- content:
- application/json:
- schema:
- properties:
- message_id:
- type: string
- description: 消息 id
- channel_id:
- type: string
- description: 子频道 id
- required:
- - message_id
- - channel_id
- responses:
- 204:
- description: 成功
- content:
- application/json:
- schema:
- $ref: 'bot.components.yaml#/components/schemas/Announces'
- tags: [GuildManagements]
- /guilds/{guild_id}/announces/{message_id}:
- parameters:
- - $ref: 'bot.components.yaml#/components/parameters/PathGuildID'
- - $ref: 'bot.components.yaml#/components/parameters/PathMessageID'
- delete:
- summary: 删除频道公告
- description: |
- 用于删除频道 guild_id 下 message_id 指定的全局公告。
- * message_id 有值时,会校验 message_id 合法性,若不校验校验 message_id,请将 message_id 设置为 all。
- externalDocs:
- url: https://bot.q.qq.com/wiki/develop/api/openapi/announces/delete_guild_announces.html
- responses:
- 204:
- description: 成功
- tags: [GuildManagements]
- /channels/{channel_id}/announces:
- parameters:
- - $ref: 'bot.components.yaml#/components/parameters/PathChannelID'
- post:
- summary: 创建子频道公告
- description: 用于将子频道 channel_id 内的某条消息设置为子频道公告。
- deprecated: true
- externalDocs:
- url: https://bot.q.qq.com/wiki/develop/api/openapi/announces/post_channel_announces.html
- requestBody:
- required: true
- content:
- application/json:
- schema:
- properties:
- message_id:
- type: string
- description: 消息 id
- required:
- - message_id
- responses:
- 200:
- description: 成功
- content:
- application/json:
- schema:
- $ref: 'bot.components.yaml#/components/schemas/Announces'
- tags: [GuildManagements]
-
- /channels/{channel_id}/announces/{message_id}:
- parameters:
- - $ref: 'bot.components.yaml#/components/parameters/PathChannelID'
- - $ref: 'bot.components.yaml#/components/parameters/PathMessageID'
- delete:
- summary: 删除子频道公告
- description: |
- 用于删除子频道 channel_id 下 message_id 指定的子频道公告。
- * message_id 有值时,会校验 message_id 合法性,若不校验校验 message_id,请将 message_id 设置为 all。
- deprecated: true
- externalDocs:
- url: 'https://bot.q.qq.com/wiki/develop/api/openapi/announces/delete_channel_announces.html'
- responses:
- 204:
- description: 成功
- tags: [GuildManagements]
-
- /channels/{channel_id}/schedules:
- parameters:
- - $ref: 'bot.components.yaml#/components/parameters/PathChannelID'
- get:
- summary: 获取频道日程列表
- description: |
- 用于获取channel_id指定的子频道中当天的日程列表。
- * 若带了参数 since,则返回结束时间在 since 之后的日程列表;若未带参数 since,则默认返回当天的日程列表。
- externalDocs:
- url: https://bot.q.qq.com/wiki/develop/api/openapi/schedule/get_schedules.html
- requestBody:
- required: true
- content:
- application/json:
- schema:
- properties:
- since:
- type: integer
- description: 起始时间戳(ms)
- responses:
- 200:
- description: 成功
- content:
- application/json:
- schema:
- type: array
- items:
- $ref: 'bot.components.yaml#/components/schemas/Schedule'
- tags:
- - Applications
- post:
- summary: 创建日程
- description: |
- 用于在 channel_id 指定的日程子频道下创建一个日程。
- * 要求操作人具有管理频道的权限,如果是机器人,则需要将机器人设置为管理员。
- * 创建成功后,返回创建成功的日程对象。
- * 创建操作频次限制: 单个管理员每天限`10`次, 单个频道每天`100`次。
- externalDocs:
- url: https://bot.q.qq.com/wiki/develop/api/openapi/schedule/post_schedule.html
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: 'bot.components.yaml#/components/schemas/ScheduleCreate'
- responses:
- 200:
- description: 成功
- content:
- application/json:
- schema:
- $ref: 'bot.components.yaml#/components/schemas/Schedule'
- tags:
- - Applications
- /channels/{channel_id}/schedules/{schedule_id}:
- parameters:
- - $ref: 'bot.components.yaml#/components/parameters/PathChannelID'
- - $ref: 'bot.components.yaml#/components/parameters/PathScheduleID'
- get:
- summary: 获取日程详情
- description: 获取日程子频道 channel_id 下 schedule_id 指定的的日程的详情。
- externalDocs:
- url: https://bot.q.qq.com/wiki/develop/api/openapi/schedule/get_schedule.html
- responses:
- 200:
- description: 成功
- content:
- application/json:
- schema:
- $ref: 'bot.components.yaml#/components/schemas/Schedule'
- tags:
- - Applications
- patch:
- summary: 修改日程
- description: |
- 用于修改日程子频道 channel_id 下 schedule_id 指定的日程的详情。
- * 要求操作人具有管理频道的权限,如果是机器人,则需要将机器人设置为管理员。
- externalDocs:
- url: https://bot.q.qq.com/wiki/develop/api/openapi/schedule/patch_schedule.html
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: 'bot.components.yaml#/components/schemas/ScheduleUpdate'
- responses:
- 200:
- description: 成功
- content:
- application/json:
- schema:
- $ref: 'bot.components.yaml#/components/schemas/Schedule'
- tags:
- - Applications
- delete:
- summary: 删除日程
- description: |
- 用于删除日程子频道 channel_id 下 schedule_id 指定的日程。
- * 要求操作人具有`管理频道的权限`,如果是机器人,则需要将机器人设置为管理员。
- externalDocs:
- url: https://bot.q.qq.com/wiki/develop/api/openapi/schedule/delete_schedule.html
- responses:
- 204:
- description: 成功
- tags:
- - Applications
- /channels/{channel_id}/audio:
- parameters:
- - $ref: 'bot.components.yaml#/components/parameters/PathChannelID'
- post:
- summary: 音频控制
- description: |
- 用于控制子频道 channel_id 下的音频。
- * 音频接口:仅限音频类机器人才能使用,后续会根据机器人类型自动开通接口权限,现如需调用,需联系平台申请权限。
- externalDocs:
- url: 'https://bot.q.qq.com/wiki/develop/api/openapi/audio/audio_control.html'
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: 'bot.components.yaml#/components/schemas/AudioControl'
- responses:
- 200:
- description: 成功
- tags:
- - Audio
- /guilds/{guild_id}/api_permission:
- parameters:
- - $ref: 'bot.components.yaml#/components/parameters/PathGuildID'
- get:
- summary: 获取频道可用权限列表
- description: |
- 用于获取机器人在频道 guild_id 内可以使用的权限列表。
- externalDocs:
- url: 'https://bot.q.qq.com/wiki/develop/api/openapi/api_permissions/get_guild_api_permission.html'
- responses:
- 200:
- description: 成功
- content:
- application/json:
- schema:
- type: array
- items:
- $ref: 'bot.components.yaml#/components/schemas/APIPermission'
- tags:
- - BotAPI
- /guilds/{guild_id}/api_permission/demand:
- parameters:
- - $ref: 'bot.components.yaml#/components/parameters/PathGuildID'
- post:
- summary: 创建频道 API 接口权限授权链接
- description: |
- 用于创建 API 接口权限授权链接,该链接指向guild_id对应的频道 。
- * 每天只能在一个频道内发 `3` 条(默认值)频道权限授权链接。
- externalDocs:
- url: https://bot.q.qq.com/wiki/develop/api/openapi/api_permissions/post_api_permission_demand.html
- requestBody:
- required: true
- content:
- application/json:
- schema:
- properties:
- channel_id:
- type: string
- description: 授权链接发送的子频道 id
- api_identify:
- $ref: 'bot.components.yaml#/components/schemas/APIPermissionDemandIdentify'
- desc:
- type: string
- description: 机器人申请对应的 API 接口权限后可以使用功能的描述
- responses:
- 200:
- description: 成功
- content:
- application/json:
- schema:
- type: array
- items:
- $ref: 'bot.components.yaml#/components/schemas/APIPermissionDemand'
- tags:
- - BotAPI
- /gateway:
- get:
- summary: 获取通用 WSS 接入点
- description: 用于获取 WSS 接入地址,通过该地址可建立 websocket 长连接。
- externalDocs:
- url: https://bot.q.qq.com/wiki/develop/api/openapi/wss/url_get.html
- responses:
- 200:
- description: 成功
- content:
- application/json:
- schema:
- type: object
- properties:
- url:
- type: string
- description: websocket 的地址
- tags:
- - BotAPI
- /gateway/bot:
- get:
- summary: 获取带分片 WSS 接入点
- description: |
- 用于获取 WSS 接入地址及相关信息,通过该地址可建立 websocket 长连接。相关信息包括:
- * 建议的分片数。
- * 目前连接数使用情况。
- externalDocs:
- url: 'https://bot.q.qq.com/wiki/develop/api/openapi/wss/shard_url_get.html'
- responses:
- 200:
- description: 成功
- content:
- application/json:
- schema:
- type: object
- properties:
- url:
- type: string
- description: websocket 的地址
- shards:
- type: integer
- description: 建议的 shard 数
- session_start_limit:
- $ref: 'bot.components.yaml#/components/schemas/SessionStartLimit'
- tags:
- - BotAPI
- /channels/{channel_id}/messages/{message_id}/reactions/{type}/{id}:
- parameters:
- - $ref: 'bot.components.yaml#/components/parameters/PathChannelID'
- - $ref: 'bot.components.yaml#/components/parameters/PathMessageID'
- - name: type
- in: path
- required: true
- schema:
- $ref: 'bot.components.yaml#/components/schemas/EmojiType'
- description: 表情类型
- - name: id
- in: path
- required: true
- schema:
- $ref: 'bot.components.yaml#/components/schemas/EmojiID'
- description: 表情ID
- put:
- tags:
- - Messages
- summary: 对消息 `message_id` 进行表情表态
- externalDocs:
- url: https://bot.q.qq.com/wiki/develop/api/openapi/reaction/put_message_reaction.html
- responses:
- 204:
- description: 成功
- delete:
- tags:
- - Messages
- summary: 删除自己对消息 `message_id` 的表情表态
- externalDocs:
- url: https://bot.q.qq.com/wiki/develop/api/openapi/reaction/delete_own_message_reaction.html
- responses:
- 204:
- description: 成功
- /channels/{channel_id}/pins/{message_id}:
- parameters:
- - $ref: 'bot.components.yaml#/components/parameters/PathChannelID'
- - $ref: 'bot.components.yaml#/components/parameters/PathMessageID'
- put:
- tags:
- - Messages
- summary: 将消息 `message_id` 添加到精华消息中
- externalDocs:
- url: https://bot.q.qq.com/wiki/develop/api/openapi/pins/put_pins_message.html
- responses:
- 200:
- description: 成功
- content:
- applicztion/json:
- schema:
- $ref: 'bot.components.yaml#/components/schemas/PinsMessage'
- delete:
- tags:
- - Messages
- summary: 将消息 `message_id` 从精华消息中删除
- externalDocs:
- url: https://bot.q.qq.com/wiki/develop/api/openapi/pins/delete_pins_message.html
- responses:
- 204:
- description: 成功
- /channels/{channel_id}/pins:
- parameters:
- - $ref: 'bot.components.yaml#/components/parameters/PathChannelID'
- get:
- tags: [Messages]
- summary: 获取精华消息列表
- externalDocs:
- url: https://bot.q.qq.com/wiki/develop/api/openapi/pins/get_pins_message.html
- responses:
- 200:
- description: 成功
- content:
- application/json:
- schema:
- $ref: 'bot.components.yaml#/components/schemas/PinsMessage'
-
-
-#================
-# 接口分类说明,不与 wiki 的分类对齐,而是按照实际的功能大类进行归类
-tags:
- - name: Messages
- description: 频道消息、私信等相关接口
- - name: UserRelations
- description: 用户关系链相关接口API,包括用户信息,成员信息,用户的频道列表等
- - name: GuildManagements
- description: 频道、子频道信息管理类的接口
- - name: Permissions
- description: 频道管理相关接口,如身份组管理,权限管理等
- - name: Applications
- description: 垂直应用类接口,如日程功能
- - name: Audio
- description: 音频相关API集合
- - name: Forums
- description: 论坛类接口,操作论坛帖子,回复等
- - name: BotAPI
- description: 机器人提供的基础接口
-
-
-# 文档地址
-externalDocs:
- description: 'Reference: QQ 机器人文档'
- url: https://bot.q.qq.com/wiki
-
-# 对象定义,parameters 和 schemes 在独立文件中定义
-components:
- securitySchemes:
- bot_auth:
- description: 机器人 API 鉴权,格式为 `Bot {appID}.{appToken}`
- type: apiKey
- name: Authorization
- in: header
- responses:
- 401:
- description: |
- 请求异常,请检查参数传递是否正确
- 403:
- description: |
- 权限检查失败,请检查如下几点:
- - 头部的 token 是否正确
- - 频道主是否授予了机器人对应的权限
- - 频道ID等ID是否正确
- 500:
- description: |
- 内部逻辑处理失败,大部分情况下可以重试,如果重试仍然失败,请检查参数是否正确
- 502:
- description: |
- 内部处理超时,请稍后重试
diff --git a/test/OpenAPI/v3.0/hubspot-events.json b/test/OpenAPI/v3.0/hubspot-events.json
deleted file mode 100644
index fa11b5e0a..000000000
--- a/test/OpenAPI/v3.0/hubspot-events.json
+++ /dev/null
@@ -1,220 +0,0 @@
-{
- "openapi" : "3.0.1",
- "info" : {
- "title" : "Custom Behavioral Events API",
- "description" : "HTTP API for triggering instances of custom behavioral events",
- "version" : "v3"
- },
- "servers" : [ {
- "url" : "https://api.hubapi.com/"
- } ],
- "tags" : [ {
- "name" : "Behavioral_Events_Tracking"
- } ],
- "paths" : {
- "/events/v3/send" : {
- "post" : {
- "tags" : [ "Behavioral_Events_Tracking" ],
- "summary" : "Sends Custom Behavioral Event",
- "description" : "Endpoint to send an instance of a behavioral event",
- "operationId" : "post-/events/v3/send",
- "requestBody" : {
- "content" : {
- "application/json" : {
- "schema" : {
- "$ref" : "#/components/schemas/BehavioralEventHttpCompletionRequest"
- }
- }
- },
- "required" : true
- },
- "responses" : {
- "204" : {
- "description" : "No content",
- "content" : { }
- },
- "default" : {
- "$ref" : "#/components/responses/Error"
- }
- },
- "security" : [ {
- "hapikey" : [ ]
- }, {
- "private_apps_legacy" : [ "analytics.behavioral_events.send" ]
- }, {
- "oauth2_legacy" : [ "analytics.behavioral_events.send" ]
- } ]
- }
- }
- },
- "components" : {
- "schemas" : {
- "ErrorDetail" : {
- "required" : [ "message" ],
- "type" : "object",
- "properties" : {
- "message" : {
- "type" : "string",
- "description" : "A human readable message describing the error along with remediation steps where appropriate"
- },
- "in" : {
- "type" : "string",
- "description" : "The name of the field or parameter in which the error was found."
- },
- "code" : {
- "type" : "string",
- "description" : "The status code associated with the error detail"
- },
- "subCategory" : {
- "type" : "string",
- "description" : "A specific category that contains more specific detail about the error"
- },
- "context" : {
- "type" : "object",
- "additionalProperties" : {
- "type" : "array",
- "items" : {
- "type" : "string"
- }
- },
- "description" : "Context about the error condition",
- "example" : {
- "missingScopes" : [ "scope1", "scope2" ]
- }
- }
- }
- },
- "BehavioralEventHttpCompletionRequest" : {
- "required" : [ "eventName", "properties" ],
- "type" : "object",
- "properties" : {
- "utk" : {
- "type" : "string",
- "description" : "User token"
- },
- "email" : {
- "type" : "string",
- "description" : "Email of visitor"
- },
- "eventName" : {
- "type" : "string",
- "description" : "Internal name of the event-type to trigger"
- },
- "properties" : {
- "type" : "object",
- "additionalProperties" : {
- "type" : "string"
- },
- "description" : "Map of properties for the event in the format property internal name - property value"
- },
- "occurredAt" : {
- "type" : "string",
- "description" : "The time when this event occurred (if any). If this isn't set, the current time will be used",
- "format" : "date-time"
- },
- "objectId" : {
- "type" : "string",
- "description" : "The object id that this event occurred on. Could be a contact id or a visitor id."
- }
- }
- },
- "Error" : {
- "required" : [ "category", "correlationId", "message" ],
- "type" : "object",
- "properties" : {
- "message" : {
- "type" : "string",
- "description" : "A human readable message describing the error along with remediation steps where appropriate",
- "example" : "An error occurred"
- },
- "correlationId" : {
- "type" : "string",
- "description" : "A unique identifier for the request. Include this value with any error reports or support tickets",
- "format" : "uuid",
- "example" : "aeb5f871-7f07-4993-9211-075dc63e7cbf"
- },
- "category" : {
- "type" : "string",
- "description" : "The error category"
- },
- "subCategory" : {
- "type" : "string",
- "description" : "A specific category that contains more specific detail about the error"
- },
- "errors" : {
- "type" : "array",
- "description" : "further information about the error",
- "items" : {
- "$ref" : "#/components/schemas/ErrorDetail"
- }
- },
- "context" : {
- "type" : "object",
- "additionalProperties" : {
- "type" : "array",
- "items" : {
- "type" : "string"
- }
- },
- "description" : "Context about the error condition",
- "example" : {
- "invalidPropertyName" : [ "propertyValue" ],
- "missingScopes" : [ "scope1", "scope2" ]
- }
- },
- "links" : {
- "type" : "object",
- "additionalProperties" : {
- "type" : "string"
- },
- "description" : "A map of link names to associated URIs containing documentation about the error or recommended remediation steps"
- }
- },
- "example" : {
- "message" : "Invalid input (details will vary based on the error)",
- "correlationId" : "aeb5f871-7f07-4993-9211-075dc63e7cbf",
- "category" : "VALIDATION_ERROR",
- "links" : {
- "knowledge-base" : "https://www.hubspot.com/products/service/knowledge-base"
- }
- }
- }
- },
- "responses" : {
- "Error" : {
- "description" : "An error occurred.",
- "content" : {
- "*/*" : {
- "schema" : {
- "$ref" : "#/components/schemas/Error"
- }
- }
- }
- }
- },
- "securitySchemes" : {
- "oauth2_legacy" : {
- "type" : "oauth2",
- "flows" : {
- "authorizationCode" : {
- "authorizationUrl" : "https://app.hubspot.com/oauth/authorize",
- "tokenUrl" : "https://api.hubapi.com/oauth/v1/token",
- "scopes" : {
- "analytics.behavioral_events.send" : "Send Behavioral Event Completions"
- }
- }
- }
- },
- "hapikey" : {
- "type" : "apiKey",
- "name" : "hapikey",
- "in" : "query"
- },
- "private_apps_legacy" : {
- "type" : "apiKey",
- "name" : "private-app-legacy",
- "in" : "header"
- }
- }
- }
-}
\ No newline at end of file
diff --git a/test/OpenAPI/v3.0/hubspot-webhooks.json b/test/OpenAPI/v3.0/hubspot-webhooks.json
deleted file mode 100644
index b65b0e6dd..000000000
--- a/test/OpenAPI/v3.0/hubspot-webhooks.json
+++ /dev/null
@@ -1,829 +0,0 @@
-{
- "openapi" : "3.0.1",
- "info" : {
- "title" : "Webhooks API",
- "description" : "Provides a way for apps to subscribe to certain change events in HubSpot. Once configured, apps will receive event payloads containing details about the changes at a specified target URL. There can only be one target URL for receiving event notifications per app.",
- "version" : "v3"
- },
- "servers" : [ {
- "url" : "https://api.hubapi.com/"
- } ],
- "tags" : [ {
- "name" : "Settings",
- "description" : "Operations to manage app-level webhook settings."
- }, {
- "name" : "Subscriptions",
- "description" : "Operations to manage event subscriptions."
- } ],
- "paths" : {
- "/webhooks/v3/{appId}/settings" : {
- "get" : {
- "tags" : [ "Settings" ],
- "operationId" : "get-/webhooks/v3/{appId}/settings_getAll",
- "parameters" : [ {
- "name" : "appId",
- "in" : "path",
- "required" : true,
- "style" : "simple",
- "explode" : false,
- "schema" : {
- "type" : "integer",
- "format" : "int32"
- }
- } ],
- "responses" : {
- "200" : {
- "description" : "successful operation",
- "content" : {
- "application/json" : {
- "schema" : {
- "$ref" : "#/components/schemas/SettingsResponse"
- }
- }
- }
- },
- "default" : {
- "$ref" : "#/components/responses/Error"
- }
- },
- "security" : [ {
- "developer_hapikey" : [ ]
- } ]
- },
- "put" : {
- "tags" : [ "Settings" ],
- "operationId" : "put-/webhooks/v3/{appId}/settings_configure",
- "parameters" : [ {
- "name" : "appId",
- "in" : "path",
- "required" : true,
- "style" : "simple",
- "explode" : false,
- "schema" : {
- "type" : "integer",
- "format" : "int32"
- }
- } ],
- "requestBody" : {
- "content" : {
- "application/json" : {
- "schema" : {
- "$ref" : "#/components/schemas/SettingsChangeRequest"
- }
- }
- },
- "required" : true
- },
- "responses" : {
- "200" : {
- "description" : "successful operation",
- "content" : {
- "application/json" : {
- "schema" : {
- "$ref" : "#/components/schemas/SettingsResponse"
- }
- }
- }
- },
- "default" : {
- "$ref" : "#/components/responses/Error"
- }
- },
- "security" : [ {
- "developer_hapikey" : [ ]
- } ]
- },
- "delete" : {
- "tags" : [ "Settings" ],
- "operationId" : "delete-/webhooks/v3/{appId}/settings_clear",
- "parameters" : [ {
- "name" : "appId",
- "in" : "path",
- "required" : true,
- "style" : "simple",
- "explode" : false,
- "schema" : {
- "type" : "integer",
- "format" : "int32"
- }
- } ],
- "responses" : {
- "204" : {
- "description" : "No content",
- "content" : { }
- },
- "default" : {
- "$ref" : "#/components/responses/Error"
- }
- },
- "security" : [ {
- "developer_hapikey" : [ ]
- } ]
- }
- },
- "/webhooks/v3/{appId}/subscriptions" : {
- "get" : {
- "tags" : [ "Subscriptions" ],
- "operationId" : "get-/webhooks/v3/{appId}/subscriptions_getAll",
- "parameters" : [ {
- "name" : "appId",
- "in" : "path",
- "required" : true,
- "style" : "simple",
- "explode" : false,
- "schema" : {
- "type" : "integer",
- "format" : "int32"
- }
- } ],
- "responses" : {
- "200" : {
- "description" : "successful operation",
- "content" : {
- "application/json" : {
- "schema" : {
- "$ref" : "#/components/schemas/SubscriptionListResponse"
- }
- }
- }
- },
- "default" : {
- "$ref" : "#/components/responses/Error"
- }
- },
- "security" : [ {
- "developer_hapikey" : [ ]
- } ]
- },
- "post" : {
- "tags" : [ "Subscriptions" ],
- "operationId" : "post-/webhooks/v3/{appId}/subscriptions_create",
- "parameters" : [ {
- "name" : "appId",
- "in" : "path",
- "required" : true,
- "style" : "simple",
- "explode" : false,
- "schema" : {
- "type" : "integer",
- "format" : "int32"
- }
- } ],
- "requestBody" : {
- "content" : {
- "application/json" : {
- "schema" : {
- "$ref" : "#/components/schemas/SubscriptionCreateRequest"
- }
- }
- },
- "required" : true
- },
- "responses" : {
- "201" : {
- "description" : "successful operation",
- "content" : {
- "application/json" : {
- "schema" : {
- "$ref" : "#/components/schemas/SubscriptionResponse"
- }
- }
- }
- },
- "default" : {
- "$ref" : "#/components/responses/Error"
- }
- },
- "security" : [ {
- "developer_hapikey" : [ ]
- } ]
- }
- },
- "/webhooks/v3/{appId}/subscriptions/batch/update" : {
- "post" : {
- "tags" : [ "Subscriptions" ],
- "operationId" : "post-/webhooks/v3/{appId}/subscriptions/batch/update_updateBatch",
- "parameters" : [ {
- "name" : "appId",
- "in" : "path",
- "required" : true,
- "style" : "simple",
- "explode" : false,
- "schema" : {
- "type" : "integer",
- "format" : "int32"
- }
- } ],
- "requestBody" : {
- "content" : {
- "application/json" : {
- "schema" : {
- "$ref" : "#/components/schemas/BatchInputSubscriptionBatchUpdateRequest"
- }
- }
- },
- "required" : true
- },
- "responses" : {
- "200" : {
- "description" : "successful operation",
- "content" : {
- "application/json" : {
- "schema" : {
- "$ref" : "#/components/schemas/BatchResponseSubscriptionResponse"
- }
- }
- }
- },
- "207" : {
- "description" : "multiple statuses",
- "content" : {
- "application/json" : {
- "schema" : {
- "$ref" : "#/components/schemas/BatchResponseSubscriptionResponseWithErrors"
- }
- }
- }
- },
- "default" : {
- "$ref" : "#/components/responses/Error"
- }
- },
- "security" : [ {
- "developer_hapikey" : [ ]
- } ]
- }
- },
- "/webhooks/v3/{appId}/subscriptions/{subscriptionId}" : {
- "get" : {
- "tags" : [ "Subscriptions" ],
- "operationId" : "get-/webhooks/v3/{appId}/subscriptions/{subscriptionId}_getById",
- "parameters" : [ {
- "name" : "subscriptionId",
- "in" : "path",
- "required" : true,
- "style" : "simple",
- "explode" : false,
- "schema" : {
- "type" : "integer",
- "format" : "int32"
- }
- }, {
- "name" : "appId",
- "in" : "path",
- "required" : true,
- "style" : "simple",
- "explode" : false,
- "schema" : {
- "type" : "integer",
- "format" : "int32"
- }
- } ],
- "responses" : {
- "200" : {
- "description" : "successful operation",
- "content" : {
- "application/json" : {
- "schema" : {
- "$ref" : "#/components/schemas/SubscriptionResponse"
- }
- }
- }
- },
- "default" : {
- "$ref" : "#/components/responses/Error"
- }
- },
- "security" : [ {
- "developer_hapikey" : [ ]
- } ]
- },
- "delete" : {
- "tags" : [ "Subscriptions" ],
- "operationId" : "delete-/webhooks/v3/{appId}/subscriptions/{subscriptionId}_archive",
- "parameters" : [ {
- "name" : "subscriptionId",
- "in" : "path",
- "required" : true,
- "style" : "simple",
- "explode" : false,
- "schema" : {
- "type" : "integer",
- "format" : "int32"
- }
- }, {
- "name" : "appId",
- "in" : "path",
- "required" : true,
- "style" : "simple",
- "explode" : false,
- "schema" : {
- "type" : "integer",
- "format" : "int32"
- }
- } ],
- "responses" : {
- "204" : {
- "description" : "No content",
- "content" : { }
- },
- "default" : {
- "$ref" : "#/components/responses/Error"
- }
- },
- "security" : [ {
- "developer_hapikey" : [ ]
- } ]
- },
- "patch" : {
- "tags" : [ "Subscriptions" ],
- "operationId" : "patch-/webhooks/v3/{appId}/subscriptions/{subscriptionId}_update",
- "parameters" : [ {
- "name" : "subscriptionId",
- "in" : "path",
- "required" : true,
- "style" : "simple",
- "explode" : false,
- "schema" : {
- "type" : "integer",
- "format" : "int32"
- }
- }, {
- "name" : "appId",
- "in" : "path",
- "required" : true,
- "style" : "simple",
- "explode" : false,
- "schema" : {
- "type" : "integer",
- "format" : "int32"
- }
- } ],
- "requestBody" : {
- "content" : {
- "application/json" : {
- "schema" : {
- "$ref" : "#/components/schemas/SubscriptionPatchRequest"
- }
- }
- },
- "required" : true
- },
- "responses" : {
- "200" : {
- "description" : "successful operation",
- "content" : {
- "application/json" : {
- "schema" : {
- "$ref" : "#/components/schemas/SubscriptionResponse"
- }
- }
- }
- },
- "default" : {
- "$ref" : "#/components/responses/Error"
- }
- },
- "security" : [ {
- "developer_hapikey" : [ ]
- } ]
- }
- }
- },
- "components" : {
- "schemas" : {
- "ThrottlingSettings" : {
- "required" : [ "maxConcurrentRequests", "period" ],
- "type" : "object",
- "properties" : {
- "maxConcurrentRequests" : {
- "type" : "integer",
- "description" : "The maximum number of HTTP requests HubSpot will attempt to make to your app in a given time frame determined by `period`.",
- "format" : "int32"
- },
- "period" : {
- "type" : "string",
- "description" : "Time scale for this setting. Can be either `SECONDLY` (per second) or `ROLLING_MINUTE` (per minute).",
- "enum" : [ "SECONDLY", "ROLLING_MINUTE" ]
- }
- },
- "description" : "Configuration details for webhook throttling."
- },
- "StandardError" : {
- "required" : [ "category", "context", "errors", "links", "message", "status" ],
- "type" : "object",
- "properties" : {
- "status" : {
- "type" : "string"
- },
- "id" : {
- "type" : "string"
- },
- "category" : {
- "$ref" : "#/components/schemas/ErrorCategory"
- },
- "subCategory" : {
- "type" : "object",
- "properties" : { }
- },
- "message" : {
- "type" : "string"
- },
- "errors" : {
- "type" : "array",
- "items" : {
- "$ref" : "#/components/schemas/ErrorDetail"
- }
- },
- "context" : {
- "type" : "object",
- "additionalProperties" : {
- "type" : "array",
- "items" : {
- "type" : "string"
- }
- }
- },
- "links" : {
- "type" : "object",
- "additionalProperties" : {
- "type" : "string"
- }
- }
- }
- },
- "BatchResponseSubscriptionResponse" : {
- "required" : [ "completedAt", "results", "startedAt", "status" ],
- "type" : "object",
- "properties" : {
- "status" : {
- "type" : "string",
- "enum" : [ "PENDING", "PROCESSING", "CANCELED", "COMPLETE" ]
- },
- "results" : {
- "type" : "array",
- "items" : {
- "$ref" : "#/components/schemas/SubscriptionResponse"
- }
- },
- "requestedAt" : {
- "type" : "string",
- "format" : "date-time"
- },
- "startedAt" : {
- "type" : "string",
- "format" : "date-time"
- },
- "completedAt" : {
- "type" : "string",
- "format" : "date-time"
- },
- "links" : {
- "type" : "object",
- "additionalProperties" : {
- "type" : "string"
- }
- }
- }
- },
- "SubscriptionListResponse" : {
- "required" : [ "results" ],
- "type" : "object",
- "properties" : {
- "results" : {
- "type" : "array",
- "description" : "List of event subscriptions for your app",
- "items" : {
- "$ref" : "#/components/schemas/SubscriptionResponse"
- }
- }
- },
- "description" : "List of event subscriptions for your app"
- },
- "Error" : {
- "required" : [ "category", "correlationId", "message" ],
- "type" : "object",
- "properties" : {
- "message" : {
- "type" : "string",
- "description" : "A human readable message describing the error along with remediation steps where appropriate",
- "example" : "An error occurred"
- },
- "correlationId" : {
- "type" : "string",
- "description" : "A unique identifier for the request. Include this value with any error reports or support tickets",
- "format" : "uuid",
- "example" : "aeb5f871-7f07-4993-9211-075dc63e7cbf"
- },
- "category" : {
- "type" : "string",
- "description" : "The error category"
- },
- "subCategory" : {
- "type" : "string",
- "description" : "A specific category that contains more specific detail about the error"
- },
- "errors" : {
- "type" : "array",
- "description" : "further information about the error",
- "items" : {
- "$ref" : "#/components/schemas/ErrorDetail"
- }
- },
- "context" : {
- "type" : "object",
- "additionalProperties" : {
- "type" : "array",
- "items" : {
- "type" : "string"
- }
- },
- "description" : "Context about the error condition",
- "example" : {
- "invalidPropertyName" : [ "propertyValue" ],
- "missingScopes" : [ "scope1", "scope2" ]
- }
- },
- "links" : {
- "type" : "object",
- "additionalProperties" : {
- "type" : "string"
- },
- "description" : "A map of link names to associated URIs containing documentation about the error or recommended remediation steps"
- }
- },
- "example" : {
- "message" : "Invalid input (details will vary based on the error)",
- "correlationId" : "aeb5f871-7f07-4993-9211-075dc63e7cbf",
- "category" : "VALIDATION_ERROR",
- "links" : {
- "knowledge-base" : "https://www.hubspot.com/products/service/knowledge-base"
- }
- }
- },
- "SettingsChangeRequest" : {
- "required" : [ "targetUrl", "throttling" ],
- "type" : "object",
- "properties" : {
- "targetUrl" : {
- "type" : "string",
- "description" : "A publicly available URL for Hubspot to call where event payloads will be delivered. See [link-so-some-doc](#) for details about the format of these event payloads."
- },
- "throttling" : {
- "$ref" : "#/components/schemas/ThrottlingSettings"
- }
- },
- "description" : "New or updated webhook settings for an app.",
- "example" : {
- "targetUrl" : "https://www.example.com/hubspot/target",
- "throttling" : {
- "maxConcurrentRequests" : 10,
- "period" : "SECONDLY"
- }
- }
- },
- "SubscriptionPatchRequest" : {
- "type" : "object",
- "properties" : {
- "active" : {
- "type" : "boolean",
- "description" : "Determines if the subscription is active or paused."
- }
- },
- "description" : "Updated details for the subscription.",
- "example" : {
- "active" : true
- }
- },
- "BatchResponseSubscriptionResponseWithErrors" : {
- "required" : [ "completedAt", "results", "startedAt", "status" ],
- "type" : "object",
- "properties" : {
- "status" : {
- "type" : "string",
- "enum" : [ "PENDING", "PROCESSING", "CANCELED", "COMPLETE" ]
- },
- "results" : {
- "type" : "array",
- "items" : {
- "$ref" : "#/components/schemas/SubscriptionResponse"
- }
- },
- "numErrors" : {
- "type" : "integer",
- "format" : "int32"
- },
- "errors" : {
- "type" : "array",
- "items" : {
- "$ref" : "#/components/schemas/StandardError"
- }
- },
- "requestedAt" : {
- "type" : "string",
- "format" : "date-time"
- },
- "startedAt" : {
- "type" : "string",
- "format" : "date-time"
- },
- "completedAt" : {
- "type" : "string",
- "format" : "date-time"
- },
- "links" : {
- "type" : "object",
- "additionalProperties" : {
- "type" : "string"
- }
- }
- }
- },
- "ErrorDetail" : {
- "required" : [ "message" ],
- "type" : "object",
- "properties" : {
- "message" : {
- "type" : "string",
- "description" : "A human readable message describing the error along with remediation steps where appropriate"
- },
- "in" : {
- "type" : "string",
- "description" : "The name of the field or parameter in which the error was found."
- },
- "code" : {
- "type" : "string",
- "description" : "The status code associated with the error detail"
- },
- "subCategory" : {
- "type" : "string",
- "description" : "A specific category that contains more specific detail about the error"
- },
- "context" : {
- "type" : "object",
- "additionalProperties" : {
- "type" : "array",
- "items" : {
- "type" : "string"
- }
- },
- "description" : "Context about the error condition",
- "example" : {
- "missingScopes" : [ "scope1", "scope2" ]
- }
- }
- }
- },
- "SettingsResponse" : {
- "required" : [ "createdAt", "targetUrl", "throttling" ],
- "type" : "object",
- "properties" : {
- "targetUrl" : {
- "type" : "string",
- "description" : "A publicly available URL for Hubspot to call where event payloads will be delivered. See [link-so-some-doc](#) for details about the format of these event payloads."
- },
- "throttling" : {
- "$ref" : "#/components/schemas/ThrottlingSettings"
- },
- "createdAt" : {
- "type" : "string",
- "description" : "When this subscription was created. Formatted as milliseconds from the [Unix epoch](#).",
- "format" : "date-time"
- },
- "updatedAt" : {
- "type" : "string",
- "description" : "When this subscription was last updated. Formatted as milliseconds from the [Unix epoch](#).",
- "format" : "date-time"
- }
- },
- "description" : "Webhook settings for an app.",
- "example" : {
- "targetUrl" : "https://www.example.com/hubspot/target",
- "throttling" : {
- "maxConcurrentRequests" : 10,
- "period" : "SECONDLY"
- },
- "createdAt" : "2020-01-24T16:27:59Z",
- "updatedAt" : "2020-01-24T16:32:43Z"
- }
- },
- "ErrorCategory" : {
- "required" : [ "httpStatus", "name" ],
- "type" : "object",
- "properties" : {
- "name" : {
- "type" : "string"
- },
- "httpStatus" : {
- "type" : "string",
- "enum" : [ "CONTINUE", "SWITCHING_PROTOCOLS", "PROCESSING", "OK", "CREATED", "ACCEPTED", "NON_AUTHORITATIVE_INFORMATION", "NO_CONTENT", "RESET_CONTENT", "PARTIAL_CONTENT", "MULTI_STATUS", "ALREADY_REPORTED", "IM_USED", "MULTIPLE_CHOICES", "MOVED_PERMANENTLY", "FOUND", "SEE_OTHER", "NOT_MODIFIED", "USE_PROXY", "TEMPORARY_REDIRECT", "PERMANENT_REDIRECT", "BAD_REQUEST", "UNAUTHORIZED", "PAYMENT_REQUIRED", "FORBIDDEN", "NOT_FOUND", "METHOD_NOT_ALLOWED", "NOT_ACCEPTABLE", "PROXY_AUTHENTICATION_REQUIRED", "REQUEST_TIMEOUT", "CONFLICT", "GONE", "LENGTH_REQUIRED", "PRECONDITION_FAILED", "REQUEST_ENTITY_TOO_LARGE", "REQUEST_URI_TOO_LONG", "UNSUPPORTED_MEDIA_TYPE", "REQUESTED_RANGE_NOT_SATISFIABLE", "EXPECTATION_FAILED", "IM_A_TEAPOT", "MISDIRECTED_REQUEST", "UNPROCESSABLE_ENTITY", "LOCKED", "FAILED_DEPENDENCY", "UPGRADE_REQUIRED", "PRECONDITION_REQUIRED", "TOO_MANY_REQUESTS", "REQUEST_HEADERS_FIELDS_TOO_LARGE", "INTERNAL_STALE_SERVICE_DISCOVERY", "UNAVAILABLE_FOR_LEGAL_REASONS", "MIGRATION_IN_PROGRESS", "INTERNAL_SERVER_ERROR", "NOT_IMPLEMENTED", "BAD_GATEWAY", "SERVICE_UNAVAILABLE", "GATEWAY_TIMEOUT", "HTTP_VERSION_NOT_SUPPORTED", "VARIANT_ALSO_NEGOTIATES", "INSUFFICIENT_STORAGE", "LOOP_DETECTED", "NOT_EXTENDED", "NETWORK_AUTHENTICATION_REQUIRED" ]
- }
- }
- },
- "BatchInputSubscriptionBatchUpdateRequest" : {
- "required" : [ "inputs" ],
- "type" : "object",
- "properties" : {
- "inputs" : {
- "type" : "array",
- "items" : {
- "$ref" : "#/components/schemas/SubscriptionBatchUpdateRequest"
- }
- }
- }
- },
- "SubscriptionBatchUpdateRequest" : {
- "required" : [ "active", "id" ],
- "type" : "object",
- "properties" : {
- "id" : {
- "type" : "integer",
- "format" : "int32"
- },
- "active" : {
- "type" : "boolean"
- }
- }
- },
- "SubscriptionResponse" : {
- "required" : [ "active", "createdAt", "eventType", "id" ],
- "type" : "object",
- "properties" : {
- "eventType" : {
- "type" : "string",
- "description" : "Type of event to listen for. Can be one of `create`, `delete`, `deletedForPrivacy`, or `propertyChange`.",
- "enum" : [ "contact.propertyChange", "company.propertyChange", "deal.propertyChange", "ticket.propertyChange", "product.propertyChange", "line_item.propertyChange", "contact.creation", "contact.deletion", "contact.privacyDeletion", "company.creation", "company.deletion", "deal.creation", "deal.deletion", "ticket.creation", "ticket.deletion", "product.creation", "product.deletion", "line_item.creation", "line_item.deletion", "conversation.creation", "conversation.deletion", "conversation.newMessage", "conversation.privacyDeletion", "conversation.propertyChange", "contact.merge", "company.merge", "deal.merge", "ticket.merge", "product.merge", "line_item.merge", "contact.restore", "company.restore", "deal.restore", "ticket.restore", "product.restore", "line_item.restore", "contact.associationChange", "company.associationChange", "deal.associationChange", "ticket.associationChange", "line_item.associationChange" ]
- },
- "propertyName" : {
- "type" : "string",
- "description" : "The internal name of the property being monitored for changes. Only applies when `eventType` is `propertyChange`."
- },
- "active" : {
- "type" : "boolean",
- "description" : "Determines if the subscription is active or paused."
- },
- "id" : {
- "type" : "string",
- "description" : "The unique ID of the subscription."
- },
- "createdAt" : {
- "type" : "string",
- "description" : "When this subscription was created. Formatted as milliseconds from the [Unix epoch](#).",
- "format" : "date-time"
- },
- "updatedAt" : {
- "type" : "string",
- "description" : "When this subscription was last updated. Formatted as milliseconds from the [Unix epoch](#).",
- "format" : "date-time"
- }
- },
- "description" : "Complete details for an event subscription."
- },
- "SubscriptionCreateRequest" : {
- "required" : [ "eventType" ],
- "type" : "object",
- "properties" : {
- "eventType" : {
- "type" : "string",
- "description" : "Type of event to listen for. Can be one of `create`, `delete`, `deletedForPrivacy`, or `propertyChange`.",
- "enum" : [ "contact.propertyChange", "company.propertyChange", "deal.propertyChange", "ticket.propertyChange", "product.propertyChange", "line_item.propertyChange", "contact.creation", "contact.deletion", "contact.privacyDeletion", "company.creation", "company.deletion", "deal.creation", "deal.deletion", "ticket.creation", "ticket.deletion", "product.creation", "product.deletion", "line_item.creation", "line_item.deletion", "conversation.creation", "conversation.deletion", "conversation.newMessage", "conversation.privacyDeletion", "conversation.propertyChange", "contact.merge", "company.merge", "deal.merge", "ticket.merge", "product.merge", "line_item.merge", "contact.restore", "company.restore", "deal.restore", "ticket.restore", "product.restore", "line_item.restore", "contact.associationChange", "company.associationChange", "deal.associationChange", "ticket.associationChange", "line_item.associationChange" ]
- },
- "propertyName" : {
- "type" : "string",
- "description" : "The internal name of the property to monitor for changes. Only applies when `eventType` is `propertyChange`."
- },
- "active" : {
- "type" : "boolean",
- "description" : "Determines if the subscription is active or paused. Defaults to false."
- }
- },
- "description" : "New webhook settings for an app.",
- "example" : {
- "active" : true,
- "eventType" : "contact.propertyChange",
- "propertyName" : "email"
- }
- }
- },
- "responses" : {
- "Error" : {
- "description" : "An error occurred.",
- "content" : {
- "*/*" : {
- "schema" : {
- "$ref" : "#/components/schemas/Error"
- }
- }
- }
- }
- },
- "securitySchemes" : {
- "developer_hapikey" : {
- "type" : "apiKey",
- "name" : "hapikey",
- "in" : "query"
- }
- }
- },
- "x-hubspot-available-client-libraries" : [ "PHP", "Node", "Python", "Ruby" ]
-}
\ No newline at end of file
diff --git a/test/smoke-tests.ps1 b/test/smoke-tests.ps1
index c96004a23..1c5a6554e 100644
--- a/test/smoke-tests.ps1
+++ b/test/smoke-tests.ps1
@@ -141,7 +141,6 @@ function RunTests
$filenames = @(
"weather",
- "bot.paths",
"petstore",
"petstore-expanded",
"petstore-minimal",
@@ -152,8 +151,7 @@ function RunTests
"link-example",
"uber",
"uspto",
- "hubspot-events",
- "hubspot-webhooks"
+ "hubspot-events"
)
$v31Filenames = @(