From 8818effae3021a82fa42dc760f0c1012f3fd6ec7 Mon Sep 17 00:00:00 2001 From: chrisdengso <8185683+chrisdengso@users.noreply.github.com> Date: Wed, 28 Oct 2026 13:39:24 +0000 Subject: [PATCH] feat: v0.6.0, API changes --- .speakeasy/gen.lock | 138 ++- .speakeasy/gen.yaml | 2 +- .speakeasy/openapi.json | 674 +++++++++++++++ CHANGELOG.md | 14 + README.md | 9 +- internal/cli/actions/execute.go | 4 +- internal/cli/authors/block.go | 106 +++ internal/cli/authors/root.go | 12 + internal/cli/authors/suspend.go | 107 +++ internal/cli/authors/unblock.go | 106 +++ internal/cli/content/allow.go | 109 +++ internal/cli/content/reject.go | 109 +++ internal/cli/content/root.go | 8 + internal/cli/root.go | 6 +- internal/cli/version.go | 2 +- internal/sdk/authors.go | 792 ++++++++++++++++++ internal/sdk/content.go | 528 ++++++++++++ .../sdk/models/operations/allowcontent.go | 120 +++ internal/sdk/models/operations/blockauthor.go | 93 ++ .../sdk/models/operations/executeaction.go | 18 + .../sdk/models/operations/rejectcontent.go | 120 +++ .../sdk/models/operations/suspendauthor.go | 102 +++ .../sdk/models/operations/unblockauthor.go | 93 ++ internal/sdk/moderationapi.go | 10 +- internal/usage/bodyschemas.go | 7 +- internal/usage/schema.go | 13 +- 26 files changed, 3270 insertions(+), 32 deletions(-) create mode 100644 internal/cli/authors/block.go create mode 100644 internal/cli/authors/suspend.go create mode 100644 internal/cli/authors/unblock.go create mode 100644 internal/cli/content/allow.go create mode 100644 internal/cli/content/reject.go create mode 100644 internal/sdk/models/operations/allowcontent.go create mode 100644 internal/sdk/models/operations/blockauthor.go create mode 100644 internal/sdk/models/operations/rejectcontent.go create mode 100644 internal/sdk/models/operations/suspendauthor.go create mode 100644 internal/sdk/models/operations/unblockauthor.go diff --git a/.speakeasy/gen.lock b/.speakeasy/gen.lock index 6b1507d..2e3a3f2 100644 --- a/.speakeasy/gen.lock +++ b/.speakeasy/gen.lock @@ -1,12 +1,12 @@ lockVersion: 2.0.0 id: f6fec850-ef66-45dd-9a18-cec7cc0801ce management: - docChecksum: ed2fd953cba3e99fb81365252925a0d4 + docChecksum: d6bce8a263f0ec11d54bdf026957ba2f docVersion: 1.1.0 speakeasyVersion: internal generationVersion: internal - releaseVersion: 0.5.0 - configChecksum: 02a247def0b187defcac9b37ddb44f1f + releaseVersion: 0.6.0 + configChecksum: 4cc78732410808a71d9e1e939e1e4dc3 repoURL: https://github.com/moderation-api/cli installationURL: https://github.com/moderation-api/cli published: true @@ -51,7 +51,7 @@ trackedFiles: internal/cli/actions/delete.go: last_write_checksum: sha1:c71d0687c59b64159890fec7b396414008aa123b internal/cli/actions/execute.go: - last_write_checksum: sha1:bb337d261800a8d89f30e634c97fac2dddec248e + last_write_checksum: sha1:4e3e4e3af01895f2f7afe1d7823f245139399f80 internal/cli/actions/list.go: last_write_checksum: sha1:cb002d17fddd5b5eabee0c3ff054a53bc74633be internal/cli/actions/retrieve.go: @@ -76,6 +76,8 @@ trackedFiles: last_write_checksum: sha1:324996d8d12662d1d801978a342779b4b8ed4572 internal/cli/auth.go: last_write_checksum: sha1:b4a5f6a9339e2f0c230ecdab7a4da8a907feb0d8 + internal/cli/authors/block.go: + last_write_checksum: sha1:0f104961102e3f7506e49a733679085a3fcde8b1 internal/cli/authors/create.go: last_write_checksum: sha1:e71ddd0d952a6ccbf8d24a93bee125156d915bff internal/cli/authors/delete.go: @@ -85,7 +87,11 @@ trackedFiles: internal/cli/authors/retrieve.go: last_write_checksum: sha1:fd4a28868414d15ce664f8f632e3da12bfdb4c1b internal/cli/authors/root.go: - last_write_checksum: sha1:95705c7a4cfdae581b8627ab77fbdabc9817504d + last_write_checksum: sha1:6f0741f5f0ff31108ba804bd8648b48fba2774b5 + internal/cli/authors/suspend.go: + last_write_checksum: sha1:b4762cf936b2ee9a623e4ffee8a497bdb93c3718 + internal/cli/authors/unblock.go: + last_write_checksum: sha1:6a3bd242b8aaa1f2782a6522a5dea213aedcea32 internal/cli/authors/update.go: last_write_checksum: sha1:0c5b4bc8f0af5f445a8b0cafe2df7da28540896a internal/cli/channels/create.go: @@ -104,8 +110,12 @@ trackedFiles: last_write_checksum: sha1:de897645adfed72311ae26864695c225bb260b5a internal/cli/configure.go: last_write_checksum: sha1:187a7058682f79b18d9da7357259b9e5f2af1309 + internal/cli/content/allow.go: + last_write_checksum: sha1:7356d2cbdeab6d89b77caca73a4dc01115920e98 + internal/cli/content/reject.go: + last_write_checksum: sha1:2721a7dcb763361facf664575b4a6c934bc26b99 internal/cli/content/root.go: - last_write_checksum: sha1:3c9d297eb0c6908e4efa9ab881dfefd9909c1595 + last_write_checksum: sha1:785c6f2a7a640f7d8c59f657a2648e31f78661c9 internal/cli/content/submit.go: last_write_checksum: sha1:5db89edd5a5f6b7867090c973744f629e4498422 internal/cli/feedback/create.go: @@ -151,9 +161,9 @@ trackedFiles: internal/cli/queue/root.go: last_write_checksum: sha1:ce2788b28359a23ff98a244ac90b71a827c16e63 internal/cli/root.go: - last_write_checksum: sha1:1bc5de6fbf6e903d6ce21034fbfd82719e4bd5e3 + last_write_checksum: sha1:e5d1b2211d9b4aaa93e95f248ce7db2af0a1844c internal/cli/version.go: - last_write_checksum: sha1:3d31261c59e8a154342e9ad6376ff39e5e54b2f7 + last_write_checksum: sha1:8ec1fd280177b7de452c747511fd0ad1c8619898 internal/cli/webhooks/create.go: last_write_checksum: sha1:98c73266c8954efcf0da2493ae41909df45a38f0 internal/cli/webhooks/delete.go: @@ -257,11 +267,11 @@ trackedFiles: internal/sdk/apikeys.go: last_write_checksum: sha1:eb2143b8ecfa3044b599462841bd90d70671bf3b internal/sdk/authors.go: - last_write_checksum: sha1:6634e98d71de59478161a5e9e0ce21c9109536af + last_write_checksum: sha1:d6ee77fd488d6dfd1225315f04691fbddf772e11 internal/sdk/channels.go: last_write_checksum: sha1:9725f5e3d5a0f5de727883345c47910195549294 internal/sdk/content.go: - last_write_checksum: sha1:e8867fac2baae5fb6d85d330f8411add76c594e7 + last_write_checksum: sha1:cccfa1b907ecf93c3f82bb8c15e599ada01e50fd internal/sdk/feedback.go: last_write_checksum: sha1:e851165bd315e8c590b4df1a5b6d40d4e2fa4a92 internal/sdk/items.go: @@ -306,6 +316,10 @@ trackedFiles: last_write_checksum: sha1:510eb3e6e24062a550cb732ea312c8963882f0e2 internal/sdk/models/operations/addwordlistwords.go: last_write_checksum: sha1:67b4548755cf27fc01bbceac7f0255b26cbdfb5d + internal/sdk/models/operations/allowcontent.go: + last_write_checksum: sha1:5d7f539c6af48590b6b166093fcc3b170a2adff6 + internal/sdk/models/operations/blockauthor.go: + last_write_checksum: sha1:3075049ab586b95c6e4c9cbcaabe8f5f3bf781fe internal/sdk/models/operations/createaction.go: last_write_checksum: sha1:dafdfd1d349f31ed9de63b05537018b44993be23 internal/sdk/models/operations/createauthor.go: @@ -337,7 +351,7 @@ trackedFiles: internal/sdk/models/operations/duplicateproject.go: last_write_checksum: sha1:f9f3c78f7ee05aa9cc2fa6bcc2f6fcb92611d643 internal/sdk/models/operations/executeaction.go: - last_write_checksum: sha1:974f2701f706a14b6cc7965ecc3c5f5930e28528 + last_write_checksum: sha1:4dcbe2995f269db017346ba97cc5da07167dbfcb internal/sdk/models/operations/getaccount.go: last_write_checksum: sha1:7b0caea7bdc17e48f831fdc4388fc4d29989c75c internal/sdk/models/operations/getaction.go: @@ -390,6 +404,8 @@ trackedFiles: last_write_checksum: sha1:df754a1f944503f21a1189dcd42099a03d37429e internal/sdk/models/operations/options.go: last_write_checksum: sha1:48bcab7f3327575bf6309176ee29015cc9477a74 + internal/sdk/models/operations/rejectcontent.go: + last_write_checksum: sha1:37290da59e6131e1b4e766e9213ee8ada70c5060 internal/sdk/models/operations/removewordlistwords.go: last_write_checksum: sha1:3f05a9e7df2be903164ba8bc07835473087b7f64 internal/sdk/models/operations/resolvequeueitem.go: @@ -398,6 +414,10 @@ trackedFiles: last_write_checksum: sha1:f43daa692b1e8203ce8ebf2adf80b79e9f04b8ae internal/sdk/models/operations/submitcontent.go: last_write_checksum: sha1:cb670d0bf2fdcd9c7ac68031c7512e2c7d0604bf + internal/sdk/models/operations/suspendauthor.go: + last_write_checksum: sha1:af885ce2174abc6695e4dbaddd7b468d50d47bdd + internal/sdk/models/operations/unblockauthor.go: + last_write_checksum: sha1:b3ad277887e69ba140a561cde376dc0d17716ed5 internal/sdk/models/operations/unresolvequeueitem.go: last_write_checksum: sha1:093fc225a3915f232e5fc18512eb0df40cfc956d internal/sdk/models/operations/updateaction.go: @@ -431,7 +451,7 @@ trackedFiles: internal/sdk/models/sdkerrors/sdkdefaulterror.go: last_write_checksum: sha1:437b3a3eacb54d8648a68f80b3cd38fa5019f006 internal/sdk/moderationapi.go: - last_write_checksum: sha1:5e3a44fff5b0c2ee7bedd9735b7436f24dd0f947 + last_write_checksum: sha1:23253dcf72960a999e32bf25f7b5bb41c216d3e4 internal/sdk/optionalnullable/optionalnullable.go: last_write_checksum: sha1:b828c94c45116e632925027e4e2c211e971e04e2 internal/sdk/optionalnullable/optionalnullable_test.go: @@ -491,9 +511,9 @@ trackedFiles: internal/testclient/testclient.go: last_write_checksum: sha1:7107e59d336235c44af152c5a61ee3896a7841fa internal/usage/bodyschemas.go: - last_write_checksum: sha1:e35857ce9ebd1c1ff7a9913f8cdb21a27aff7db3 + last_write_checksum: sha1:27959b7df8d97c2b4639655c81c330a43114d7d6 internal/usage/schema.go: - last_write_checksum: sha1:83417c8703b1d61777c96648b8db8a955b562791 + last_write_checksum: sha1:3ca5ea9c7c1891b13d1e360a33e7075bd5db1f0b scripts/install.ps1: last_write_checksum: sha1:bd3a52c28f647ed881c6e8d72abf2d84216976c9 scripts/install.sh: @@ -1457,4 +1477,94 @@ examples: application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://shameless-excess.net"} "429": application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://breakable-insolence.info"} + blockAuthor: + speakeasy-default-block-author: + parameters: + path: + id: "" + requestBody: + application/json: {} + responses: + "200": + application/json: {"success": false} + "400": + application/json: {"type": "invalid_request_error", "code": "invalid_request", "message": "Invalid input data", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://mammoth-appliance.org/"} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://clumsy-ectoderm.com/"} + "404": + application/json: {"type": "not_found_error", "code": "resource_not_found", "message": "Not found", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://square-custody.com"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://mysterious-dime.name/"} + suspendAuthor: + speakeasy-default-suspend-author: + parameters: + path: + id: "" + requestBody: + application/json: {} + responses: + "200": + application/json: {"success": true} + "400": + application/json: {"type": "invalid_request_error", "code": "invalid_request", "message": "Invalid input data", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://cluttered-hubris.name/"} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://far-off-farm.net/"} + "404": + application/json: {"type": "not_found_error", "code": "resource_not_found", "message": "Not found", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://scented-farmer.info"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://miserly-cuckoo.org/"} + unblockAuthor: + speakeasy-default-unblock-author: + parameters: + path: + id: "" + requestBody: + application/json: {} + responses: + "200": + application/json: {"success": false} + "400": + application/json: {"type": "invalid_request_error", "code": "invalid_request", "message": "Invalid input data", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://utter-cornet.org"} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://oddball-bathhouse.biz/"} + "404": + application/json: {"type": "not_found_error", "code": "resource_not_found", "message": "Not found", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://apprehensive-fisherman.info/"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://unhappy-simple.net/"} + rejectContent: + speakeasy-default-reject-content: + parameters: + path: + id: "" + requestBody: + application/json: {} + responses: + "200": + application/json: {"success": true} + "400": + application/json: {"type": "invalid_request_error", "code": "invalid_request", "message": "Invalid input data", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://expert-pasta.com"} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://vibrant-adaptation.name"} + "404": + application/json: {"type": "not_found_error", "code": "resource_not_found", "message": "Not found", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://boiling-gym.com/"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://spanish-backburn.net"} + allowContent: + speakeasy-default-allow-content: + parameters: + path: + id: "" + requestBody: + application/json: {} + responses: + "200": + application/json: {"success": true} + "400": + application/json: {"type": "invalid_request_error", "code": "invalid_request", "message": "Invalid input data", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://earnest-fishery.com"} + "401": + application/json: {"type": "authentication_error", "code": "unauthorized", "message": "Authorization not provided", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://meager-climb.biz/"} + "404": + application/json: {"type": "not_found_error", "code": "resource_not_found", "message": "Not found", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://confused-bell.org"} + "429": + application/json: {"type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "h8fkz6kkzoa446ffve37atys", "doc_url": "https://nervous-partridge.org/"} examplesVersion: 1.0.2 diff --git a/.speakeasy/gen.yaml b/.speakeasy/gen.yaml index a2b7d65..14a289f 100644 --- a/.speakeasy/gen.yaml +++ b/.speakeasy/gen.yaml @@ -32,7 +32,7 @@ generation: generateNewTests: true skipResponseBodyAssertions: false cli: - version: 0.5.0 + version: 0.6.0 additionalDependencies: {} agentEnvironmentDetection: true arrayFlagFormat: repeatable diff --git a/.speakeasy/openapi.json b/.speakeasy/openapi.json index cabb255..9a2cf7e 100644 --- a/.speakeasy/openapi.json +++ b/.speakeasy/openapi.json @@ -5103,6 +5103,14 @@ "description": "Optional duration in milliseconds for actions with timeouts", "type": "number", "minimum": 0 + }, + "moderator": { + "description": "The user ID or email of the organization member who made the decision. The action is recorded as theirs.", + "type": "string" + }, + "createCasebookCase": { + "description": "Record an allow or reject decision as a casebook case. Defaults to true when `moderator` is set, else false. Without `moderator`, the case is recorded as System.", + "type": "boolean" } }, "required": [ @@ -5342,6 +5350,672 @@ "x-speakeasy-ignore": true } }, + "/content/{id}/reject": { + "post": { + "operationId": "rejectContent", + "summary": "Reject content", + "description": "Reject a content item with the built-in reject action, and resolve it in the review queue.", + "tags": [ + "Content" + ], + "security": [ + { + "Authorization": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string", + "description": "The content ID you sent when you submitted the content, or the ID Moderation API assigned." + }, + "required": true, + "description": "The content ID you sent when you submitted the content, or the ID Moderation API assigned." + }, + { + "in": "header", + "name": "Project-Id", + "required": false, + "schema": { + "type": "string" + }, + "description": "The project to act on, by ID or name. Required with an organization key unless the key reaches only one project; list projects with GET /projects. A project key acts on its own project and needs no value: naming another one is an error.", + "x-speakeasy-name-override": "projectId" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "reason": { + "description": "Why the action was taken. Saved with the action and sent in the webhook event.", + "type": "string" + }, + "queueId": { + "description": "The queue the content is reviewed in. Defaults to the project's default queue.", + "type": "string" + }, + "moderator": { + "description": "The user ID or email of the organization member who made the decision. The action is recorded as theirs.", + "type": "string" + }, + "createCasebookCase": { + "description": "Record an allow or reject decision as a casebook case. Defaults to true when `moderator` is set, else false. Without `moderator`, the case is recorded as System.", + "type": "boolean" + } + } + }, + "example": { + "reason": "Spam" + } + } + } + }, + "responses": { + "200": { + "description": "Action executed successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "description": "Whether the action was executed successfully" + } + }, + "required": [ + "success" + ], + "additionalProperties": false, + "description": "Execution result" + } + } + } + }, + "400": { + "description": "Invalid input data", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "401": { + "description": "Authorization not provided", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + }, + "429": { + "description": "Too many requests. Retry after the number of seconds in the `Retry-After` header.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitError" + } + } + } + } + }, + "x-speakeasy-group": "content", + "x-speakeasy-name-override": "reject", + "x-speakeasy-mcp": { + "title": "Reject content", + "scopes": [ + "destructive" + ], + "readOnlyHint": false, + "destructiveHint": true + } + } + }, + "/content/{id}/allow": { + "post": { + "operationId": "allowContent", + "summary": "Allow content", + "description": "Allow a content item with the built-in allow action, and resolve it in the review queue.", + "tags": [ + "Content" + ], + "security": [ + { + "Authorization": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string", + "description": "The content ID you sent when you submitted the content, or the ID Moderation API assigned." + }, + "required": true, + "description": "The content ID you sent when you submitted the content, or the ID Moderation API assigned." + }, + { + "in": "header", + "name": "Project-Id", + "required": false, + "schema": { + "type": "string" + }, + "description": "The project to act on, by ID or name. Required with an organization key unless the key reaches only one project; list projects with GET /projects. A project key acts on its own project and needs no value: naming another one is an error.", + "x-speakeasy-name-override": "projectId" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "reason": { + "description": "Why the action was taken. Saved with the action and sent in the webhook event.", + "type": "string" + }, + "queueId": { + "description": "The queue the content is reviewed in. Defaults to the project's default queue.", + "type": "string" + }, + "moderator": { + "description": "The user ID or email of the organization member who made the decision. The action is recorded as theirs.", + "type": "string" + }, + "createCasebookCase": { + "description": "Record an allow or reject decision as a casebook case. Defaults to true when `moderator` is set, else false. Without `moderator`, the case is recorded as System.", + "type": "boolean" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Action executed successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "description": "Whether the action was executed successfully" + } + }, + "required": [ + "success" + ], + "additionalProperties": false, + "description": "Execution result" + } + } + } + }, + "400": { + "description": "Invalid input data", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "401": { + "description": "Authorization not provided", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + }, + "429": { + "description": "Too many requests. Retry after the number of seconds in the `Retry-After` header.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitError" + } + } + } + } + }, + "x-speakeasy-group": "content", + "x-speakeasy-name-override": "allow", + "x-speakeasy-mcp": { + "title": "Allow content", + "scopes": [ + "write" + ], + "readOnlyHint": false, + "destructiveHint": false + } + } + }, + "/authors/{id}/block": { + "post": { + "operationId": "blockAuthor", + "summary": "Block author", + "description": "Block an author with the built-in block action. A blocked author cannot post new content until unblocked.", + "tags": [ + "Authors" + ], + "security": [ + { + "Authorization": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string", + "description": "Either external ID or the ID assigned by moderation API." + }, + "required": true, + "description": "Either external ID or the ID assigned by moderation API." + }, + { + "in": "header", + "name": "Project-Id", + "required": false, + "schema": { + "type": "string" + }, + "description": "The project to act on, by ID or name. Required with an organization key unless the key reaches only one project; list projects with GET /projects. A project key acts on its own project and needs no value: naming another one is an error.", + "x-speakeasy-name-override": "projectId" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "reason": { + "description": "Why the action was taken. Saved with the action and sent in the webhook event.", + "type": "string" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Action executed successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "description": "Whether the action was executed successfully" + } + }, + "required": [ + "success" + ], + "additionalProperties": false, + "description": "Execution result" + } + } + } + }, + "400": { + "description": "Invalid input data", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "401": { + "description": "Authorization not provided", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + }, + "429": { + "description": "Too many requests. Retry after the number of seconds in the `Retry-After` header.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitError" + } + } + } + } + }, + "x-speakeasy-group": "authors", + "x-speakeasy-name-override": "block", + "x-speakeasy-mcp": { + "title": "Block author", + "scopes": [ + "destructive" + ], + "readOnlyHint": false, + "destructiveHint": true + } + } + }, + "/authors/{id}/suspend": { + "post": { + "operationId": "suspendAuthor", + "summary": "Suspend author", + "description": "Block an author for a limited time with the built-in suspend action.", + "tags": [ + "Authors" + ], + "security": [ + { + "Authorization": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string", + "description": "Either external ID or the ID assigned by moderation API." + }, + "required": true, + "description": "Either external ID or the ID assigned by moderation API." + }, + { + "in": "header", + "name": "Project-Id", + "required": false, + "schema": { + "type": "string" + }, + "description": "The project to act on, by ID or name. Required with an organization key unless the key reaches only one project; list projects with GET /projects. A project key acts on its own project and needs no value: naming another one is an error.", + "x-speakeasy-name-override": "projectId" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "reason": { + "description": "Why the action was taken. Saved with the action and sent in the webhook event.", + "type": "string" + }, + "duration": { + "description": "How long the suspension lasts, in milliseconds. Defaults to 24 hours.", + "type": "number", + "minimum": 0 + } + } + }, + "example": { + "reason": "Violation of community guidelines", + "duration": 604800000 + } + } + } + }, + "responses": { + "200": { + "description": "Action executed successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "description": "Whether the action was executed successfully" + } + }, + "required": [ + "success" + ], + "additionalProperties": false, + "description": "Execution result" + } + } + } + }, + "400": { + "description": "Invalid input data", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "401": { + "description": "Authorization not provided", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + }, + "429": { + "description": "Too many requests. Retry after the number of seconds in the `Retry-After` header.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitError" + } + } + } + } + }, + "x-speakeasy-group": "authors", + "x-speakeasy-name-override": "suspend", + "x-speakeasy-mcp": { + "title": "Suspend author", + "scopes": [ + "destructive" + ], + "readOnlyHint": false, + "destructiveHint": true + } + } + }, + "/authors/{id}/unblock": { + "post": { + "operationId": "unblockAuthor", + "summary": "Unblock author", + "description": "Unblock a blocked or suspended author with the built-in unblock action, so they can post new content again.", + "tags": [ + "Authors" + ], + "security": [ + { + "Authorization": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string", + "description": "Either external ID or the ID assigned by moderation API." + }, + "required": true, + "description": "Either external ID or the ID assigned by moderation API." + }, + { + "in": "header", + "name": "Project-Id", + "required": false, + "schema": { + "type": "string" + }, + "description": "The project to act on, by ID or name. Required with an organization key unless the key reaches only one project; list projects with GET /projects. A project key acts on its own project and needs no value: naming another one is an error.", + "x-speakeasy-name-override": "projectId" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "reason": { + "description": "Why the action was taken. Saved with the action and sent in the webhook event.", + "type": "string" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Action executed successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "description": "Whether the action was executed successfully" + } + }, + "required": [ + "success" + ], + "additionalProperties": false, + "description": "Execution result" + } + } + } + }, + "400": { + "description": "Invalid input data", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "401": { + "description": "Authorization not provided", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + }, + "429": { + "description": "Too many requests. Retry after the number of seconds in the `Retry-After` header.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitError" + } + } + } + } + }, + "x-speakeasy-group": "authors", + "x-speakeasy-name-override": "unblock", + "x-speakeasy-mcp": { + "title": "Unblock author", + "scopes": [ + "write" + ], + "readOnlyHint": false, + "destructiveHint": false + } + } + }, "/webhooks": { "get": { "operationId": "listWebhooks", diff --git a/CHANGELOG.md b/CHANGELOG.md index a74fce1..5632536 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## 0.6.0 (2026-10-28) + +Full Changelog: [v0.5.0...v0.6.0](https://github.com/moderation-api/cli/compare/v0.5.0...v0.6.0) + +### API changes + +- `POST /actions/execute`: added the new optional request property `createCasebookCase` +- `POST /actions/execute`: added the new optional request property `moderator` +- `POST /authors/{id}/block`: endpoint added +- `POST /authors/{id}/suspend`: endpoint added +- `POST /authors/{id}/unblock`: endpoint added +- `POST /content/{id}/allow`: endpoint added +- `POST /content/{id}/reject`: endpoint added + ## 0.5.0 (2026-10-26) Full Changelog: [v0.4.0...v0.5.0](https://github.com/moderation-api/cli/compare/v0.4.0...v0.5.0) diff --git a/README.md b/README.md index 566ebcf..81a7f11 100644 --- a/README.md +++ b/README.md @@ -377,6 +377,9 @@ modapi projects list --secret-key "$MODAPI_ORGANIZATION_KEY" * [`retrieve`](docs/modapi_authors_retrieve.md) - Get author details * [`update`](docs/modapi_authors_update.md) - Update author details * [`delete`](docs/modapi_authors_delete.md) - Delete an author + * [`block`](docs/modapi_authors_block.md) - Block author + * [`suspend`](docs/modapi_authors_suspend.md) - Suspend author + * [`unblock`](docs/modapi_authors_unblock.md) - Unblock author * [`feedback`](docs/modapi_feedback.md) - Operations for feedback * [`create`](docs/modapi_feedback_create.md) - Send feedback * [`queue`](docs/modapi_queue.md) - Operations for queue @@ -395,6 +398,10 @@ modapi projects list --secret-key "$MODAPI_ORGANIZATION_KEY" * [`update`](docs/modapi_actions_update.md) - Update an action * [`delete`](docs/modapi_actions_delete.md) - Delete an action * [`execute`](docs/modapi_actions_execute.md) - Execute moderation action +* [`content`](docs/modapi_content.md) - Operations for content + * [`reject`](docs/modapi_content_reject.md) - Reject content + * [`allow`](docs/modapi_content_allow.md) - Allow content + * [`submit`](docs/modapi_content_submit.md) - Submit content * [`webhooks`](docs/modapi_webhooks.md) - Operations for webhooks * [`list`](docs/modapi_webhooks_list.md) - List webhooks * [`create`](docs/modapi_webhooks_create.md) - Create a webhook @@ -414,8 +421,6 @@ modapi projects list --secret-key "$MODAPI_ORGANIZATION_KEY" * [`words`](docs/modapi_wordlist_words.md) - Operations for words * [`add`](docs/modapi_wordlist_words_add.md) - Add words to wordlist * [`remove`](docs/modapi_wordlist_words_remove.md) - Remove words from wordlist -* [`content`](docs/modapi_content.md) - Operations for content - * [`submit`](docs/modapi_content_submit.md) - Submit content * [`projects`](docs/modapi_projects.md) - Operations for projects * [`list`](docs/modapi_projects_list.md) - List projects * [`create`](docs/modapi_projects_create.md) - Create a project diff --git a/internal/cli/actions/execute.go b/internal/cli/actions/execute.go index 3d020e8..9183aec 100644 --- a/internal/cli/actions/execute.go +++ b/internal/cli/actions/execute.go @@ -16,11 +16,13 @@ import ( var executeCmdMeta = []flagutil.FlagMeta{ {FlagName: "project-id", Shorthand: "p", FieldPath: "ProjectID", Kind: flagutil.FlagKindString, Optional: true, Description: "The project to act on, by ID or name. Required with an organization key unless the key reaches only one project; list projects with GET /projects. A project key acts on its own project and needs no value: naming another one is an error."}, {FlagName: "action-key", FieldPath: "Body.ActionKey", Kind: flagutil.FlagKindString, Required: true, Description: "ID or key of the action to execute [required]"}, - {FlagName: "content-ids", Shorthand: "c", FieldPath: "Body.ContentIds", Kind: flagutil.FlagKindStringArray, Optional: true, Description: "IDs of the content items to apply the action to. Provide this or authorIds."}, + {FlagName: "content-ids", FieldPath: "Body.ContentIds", Kind: flagutil.FlagKindStringArray, Optional: true, Description: "IDs of the content items to apply the action to. Provide this or authorIds."}, {FlagName: "author-ids", FieldPath: "Body.AuthorIds", Kind: flagutil.FlagKindStringArray, Optional: true, Description: "IDs of the authors to apply the action to. Provide this or contentIds."}, {FlagName: "value", Shorthand: "v", FieldPath: "Body.Value", Kind: flagutil.FlagKindString, Optional: true, Description: "Optional value to provide with the action"}, {FlagName: "queue-id", FieldPath: "Body.QueueID", Kind: flagutil.FlagKindString, Optional: true, Description: "Optional queue ID if the action is queue-specific"}, {FlagName: "duration", FieldPath: "Body.Duration", Kind: flagutil.FlagKindFloat64, Optional: true, HasMinimum: true, Minimum: 0, Description: "Optional duration in milliseconds for actions with timeouts"}, + {FlagName: "moderator", Shorthand: "m", FieldPath: "Body.Moderator", Kind: flagutil.FlagKindString, Optional: true, Description: "The user ID or email of the organization member who made the decision. The action is recorded as theirs."}, + {FlagName: "create-casebook-case", FieldPath: "Body.CreateCasebookCase", Kind: flagutil.FlagKindBool, Optional: true, Description: "Record an allow or reject decision as a casebook case. Defaults to true when 'moderator' is set, else false. Without 'moderator', the case is recorded as System."}, } // initExecuteCmd initializes the execute command. diff --git a/internal/cli/authors/block.go b/internal/cli/authors/block.go new file mode 100644 index 0000000..720ccb9 --- /dev/null +++ b/internal/cli/authors/block.go @@ -0,0 +1,106 @@ +// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + +package authors + +import ( + "fmt" + "github.com/moderation-api/cli/internal/client" + "github.com/moderation-api/cli/internal/flagutil" + "github.com/moderation-api/cli/internal/interactive" + "github.com/moderation-api/cli/internal/output" + "github.com/moderation-api/cli/internal/sdk" + "github.com/moderation-api/cli/internal/sdk/models/operations" + "github.com/moderation-api/cli/internal/usage" + "github.com/spf13/cobra" +) + +var blockCmdMeta = []flagutil.FlagMeta{ + {FlagName: "id", Shorthand: "i", FieldPath: "ID", Kind: flagutil.FlagKindString, Required: true, Description: "Either external ID or the ID assigned by moderation API. [required]"}, + {FlagName: "project-id", Shorthand: "p", FieldPath: "ProjectID", Kind: flagutil.FlagKindString, Optional: true, Description: "The project to act on, by ID or name. Required with an organization key unless the key reaches only one project; list projects with GET /projects. A project key acts on its own project and needs no value: naming another one is an error."}, + {FlagName: "reason", Shorthand: "r", FieldPath: "Body.Reason", Kind: flagutil.FlagKindString, Optional: true, Description: "Why the action was taken. Saved with the action and sent in the webhook event."}, +} + +// initBlockCmd initializes the block command. +func initBlockCmd(parent *cobra.Command) error { + var cmd = &cobra.Command{ + Use: "block [id]", + Short: "Block author", + Long: "Block an author with the built-in block action. A blocked author cannot post new content until unblocked.", + Example: " modapi authors block --id ", + Args: flagutil.PositionalFlagArgs, + RunE: runBlockCmd, + Annotations: map[string]string{ + "speakeasy_operation": "blockAuthor", + }, + } + flagutil.RegisterFlags(cmd, blockCmdMeta) + if err := flagutil.ValidateMeta[operations.BlockAuthorRequest](blockCmdMeta); err != nil { + return fmt.Errorf("invalid metadata for block: %w", err) + } + cmd.Flags().String("body", "", "Request body as JSON (alternative to individual flags). Can also be provided via stdin; @path reads a file, @- reads stdin to EOF. Use --schema to print the exact JSON Schema.") + _ = flagutil.AnnotatePromptFlag(cmd, "body", flagutil.PromptFlagSpec{Required: false, Kind: "json", BodyFlag: true}) + cmd.Annotations[flagutil.AnnotationWholeBodyFlag] = "body" + if err := flagutil.AnnotateBodyFields(cmd, blockCmdMeta, "Body", "body"); err != nil { + return fmt.Errorf("annotate body fields for block: %w", err) + } + cmd.Flags().Bool("schema", false, "Print the exact JSON Schema of the request body and exit") + _ = flagutil.AnnotatePromptFlag(cmd, "schema", flagutil.PromptFlagSpec{Kind: "bool", DocSurface: true}) + if err := flagutil.DeclarePositionalFlag(cmd, "id", "Either external ID or the ID assigned by moderation API. (or pass it as the [id] argument)", true); err != nil { + return err + } + if err := interactive.Declare(cmd, interactive.CommandSpec{Args: []interactive.ArgSpec{ + {Name: "id", Summary: "Either external ID or the ID assigned by moderation API.", Required: true, SatisfiedBy: []string{"id"}}, + }}); err != nil { + return fmt.Errorf("declare interactive arguments for block: %w", err) + } + parent.AddCommand(cmd) + return nil +} + +// runBlockCmd executes the block command. +func runBlockCmd(cmd *cobra.Command, args []string) error { + if usage.UsageRequested(cmd) { + return usage.EmitSchema(cmd, cmd.OutOrStdout()) + } + if requested, _ := cmd.Flags().GetBool("schema"); requested { + return usage.EmitBodySchema(cmd.OutOrStdout(), "blockAuthor") + } + if err := flagutil.ResolvePositionalFlag(cmd, args); err != nil { + return err + } + req, err := flagutil.BuildRequest[operations.BlockAuthorRequest](cmd, blockCmdMeta, "Body", "body") + if err != nil { + return flagutil.WithCLIValidation(err) + } + s, err := client.NewClient(cmd) + if err != nil { + return err + } + sdkOpts, err := output.PrepareCallOpts(cmd) + if err != nil { + return err + } + // Dry-run: force skip deserialization so the synthetic empty response + // does not cause parse failures in typed response handling. + if client.IsDryRun(cmd) { + sdkOpts = append(sdkOpts, operations.WithSkipDeserialization()) + } + if err := output.ValidateGlobalServerIndex(cmd, len(sdk.ServerList)); err != nil { + return err + } + if output.WantsRawJSON(cmd) { + sdkOpts = append(sdkOpts, operations.WithSkipDeserialization()) + } + stopProgress := output.StartRequestProgress(cmd) + defer stopProgress() + res, err := s.Authors.Block(cmd.Context(), *req, sdkOpts...) + stopProgress() + if err != nil { + return output.Error(cmd, err) + } + + if err := output.Result(cmd, res); err != nil { + return err + } + return nil +} diff --git a/internal/cli/authors/root.go b/internal/cli/authors/root.go index bef2a71..a880f4d 100644 --- a/internal/cli/authors/root.go +++ b/internal/cli/authors/root.go @@ -42,6 +42,18 @@ func InitAuthorsRoot(parent *cobra.Command) error { return err } + if err := initBlockCmd(AuthorsCmd); err != nil { + return err + } + + if err := initSuspendCmd(AuthorsCmd); err != nil { + return err + } + + if err := initUnblockCmd(AuthorsCmd); err != nil { + return err + } + parent.AddCommand(AuthorsCmd) return nil } diff --git a/internal/cli/authors/suspend.go b/internal/cli/authors/suspend.go new file mode 100644 index 0000000..7051a85 --- /dev/null +++ b/internal/cli/authors/suspend.go @@ -0,0 +1,107 @@ +// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + +package authors + +import ( + "fmt" + "github.com/moderation-api/cli/internal/client" + "github.com/moderation-api/cli/internal/flagutil" + "github.com/moderation-api/cli/internal/interactive" + "github.com/moderation-api/cli/internal/output" + "github.com/moderation-api/cli/internal/sdk" + "github.com/moderation-api/cli/internal/sdk/models/operations" + "github.com/moderation-api/cli/internal/usage" + "github.com/spf13/cobra" +) + +var suspendCmdMeta = []flagutil.FlagMeta{ + {FlagName: "id", Shorthand: "i", FieldPath: "ID", Kind: flagutil.FlagKindString, Required: true, Description: "Either external ID or the ID assigned by moderation API. [required]"}, + {FlagName: "project-id", Shorthand: "p", FieldPath: "ProjectID", Kind: flagutil.FlagKindString, Optional: true, Description: "The project to act on, by ID or name. Required with an organization key unless the key reaches only one project; list projects with GET /projects. A project key acts on its own project and needs no value: naming another one is an error."}, + {FlagName: "reason", Shorthand: "r", FieldPath: "Body.Reason", Kind: flagutil.FlagKindString, Optional: true, Description: "Why the action was taken. Saved with the action and sent in the webhook event."}, + {FlagName: "duration", FieldPath: "Body.Duration", Kind: flagutil.FlagKindFloat64, Optional: true, HasMinimum: true, Minimum: 0, Description: "How long the suspension lasts, in milliseconds. Defaults to 24 hours."}, +} + +// initSuspendCmd initializes the suspend command. +func initSuspendCmd(parent *cobra.Command) error { + var cmd = &cobra.Command{ + Use: "suspend [id]", + Short: "Suspend author", + Long: "Block an author for a limited time with the built-in suspend action.", + Example: " modapi authors suspend --id ", + Args: flagutil.PositionalFlagArgs, + RunE: runSuspendCmd, + Annotations: map[string]string{ + "speakeasy_operation": "suspendAuthor", + }, + } + flagutil.RegisterFlags(cmd, suspendCmdMeta) + if err := flagutil.ValidateMeta[operations.SuspendAuthorRequest](suspendCmdMeta); err != nil { + return fmt.Errorf("invalid metadata for suspend: %w", err) + } + cmd.Flags().String("body", "", "Request body as JSON (alternative to individual flags). Can also be provided via stdin; @path reads a file, @- reads stdin to EOF. Use --schema to print the exact JSON Schema.") + _ = flagutil.AnnotatePromptFlag(cmd, "body", flagutil.PromptFlagSpec{Required: false, Kind: "json", BodyFlag: true}) + cmd.Annotations[flagutil.AnnotationWholeBodyFlag] = "body" + if err := flagutil.AnnotateBodyFields(cmd, suspendCmdMeta, "Body", "body"); err != nil { + return fmt.Errorf("annotate body fields for suspend: %w", err) + } + cmd.Flags().Bool("schema", false, "Print the exact JSON Schema of the request body and exit") + _ = flagutil.AnnotatePromptFlag(cmd, "schema", flagutil.PromptFlagSpec{Kind: "bool", DocSurface: true}) + if err := flagutil.DeclarePositionalFlag(cmd, "id", "Either external ID or the ID assigned by moderation API. (or pass it as the [id] argument)", true); err != nil { + return err + } + if err := interactive.Declare(cmd, interactive.CommandSpec{Args: []interactive.ArgSpec{ + {Name: "id", Summary: "Either external ID or the ID assigned by moderation API.", Required: true, SatisfiedBy: []string{"id"}}, + }}); err != nil { + return fmt.Errorf("declare interactive arguments for suspend: %w", err) + } + parent.AddCommand(cmd) + return nil +} + +// runSuspendCmd executes the suspend command. +func runSuspendCmd(cmd *cobra.Command, args []string) error { + if usage.UsageRequested(cmd) { + return usage.EmitSchema(cmd, cmd.OutOrStdout()) + } + if requested, _ := cmd.Flags().GetBool("schema"); requested { + return usage.EmitBodySchema(cmd.OutOrStdout(), "suspendAuthor") + } + if err := flagutil.ResolvePositionalFlag(cmd, args); err != nil { + return err + } + req, err := flagutil.BuildRequest[operations.SuspendAuthorRequest](cmd, suspendCmdMeta, "Body", "body") + if err != nil { + return flagutil.WithCLIValidation(err) + } + s, err := client.NewClient(cmd) + if err != nil { + return err + } + sdkOpts, err := output.PrepareCallOpts(cmd) + if err != nil { + return err + } + // Dry-run: force skip deserialization so the synthetic empty response + // does not cause parse failures in typed response handling. + if client.IsDryRun(cmd) { + sdkOpts = append(sdkOpts, operations.WithSkipDeserialization()) + } + if err := output.ValidateGlobalServerIndex(cmd, len(sdk.ServerList)); err != nil { + return err + } + if output.WantsRawJSON(cmd) { + sdkOpts = append(sdkOpts, operations.WithSkipDeserialization()) + } + stopProgress := output.StartRequestProgress(cmd) + defer stopProgress() + res, err := s.Authors.Suspend(cmd.Context(), *req, sdkOpts...) + stopProgress() + if err != nil { + return output.Error(cmd, err) + } + + if err := output.Result(cmd, res); err != nil { + return err + } + return nil +} diff --git a/internal/cli/authors/unblock.go b/internal/cli/authors/unblock.go new file mode 100644 index 0000000..57f0f52 --- /dev/null +++ b/internal/cli/authors/unblock.go @@ -0,0 +1,106 @@ +// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + +package authors + +import ( + "fmt" + "github.com/moderation-api/cli/internal/client" + "github.com/moderation-api/cli/internal/flagutil" + "github.com/moderation-api/cli/internal/interactive" + "github.com/moderation-api/cli/internal/output" + "github.com/moderation-api/cli/internal/sdk" + "github.com/moderation-api/cli/internal/sdk/models/operations" + "github.com/moderation-api/cli/internal/usage" + "github.com/spf13/cobra" +) + +var unblockCmdMeta = []flagutil.FlagMeta{ + {FlagName: "id", Shorthand: "i", FieldPath: "ID", Kind: flagutil.FlagKindString, Required: true, Description: "Either external ID or the ID assigned by moderation API. [required]"}, + {FlagName: "project-id", Shorthand: "p", FieldPath: "ProjectID", Kind: flagutil.FlagKindString, Optional: true, Description: "The project to act on, by ID or name. Required with an organization key unless the key reaches only one project; list projects with GET /projects. A project key acts on its own project and needs no value: naming another one is an error."}, + {FlagName: "reason", Shorthand: "r", FieldPath: "Body.Reason", Kind: flagutil.FlagKindString, Optional: true, Description: "Why the action was taken. Saved with the action and sent in the webhook event."}, +} + +// initUnblockCmd initializes the unblock command. +func initUnblockCmd(parent *cobra.Command) error { + var cmd = &cobra.Command{ + Use: "unblock [id]", + Short: "Unblock author", + Long: "Unblock a blocked or suspended author with the built-in unblock action, so they can post new content again.", + Example: " modapi authors unblock --id ", + Args: flagutil.PositionalFlagArgs, + RunE: runUnblockCmd, + Annotations: map[string]string{ + "speakeasy_operation": "unblockAuthor", + }, + } + flagutil.RegisterFlags(cmd, unblockCmdMeta) + if err := flagutil.ValidateMeta[operations.UnblockAuthorRequest](unblockCmdMeta); err != nil { + return fmt.Errorf("invalid metadata for unblock: %w", err) + } + cmd.Flags().String("body", "", "Request body as JSON (alternative to individual flags). Can also be provided via stdin; @path reads a file, @- reads stdin to EOF. Use --schema to print the exact JSON Schema.") + _ = flagutil.AnnotatePromptFlag(cmd, "body", flagutil.PromptFlagSpec{Required: false, Kind: "json", BodyFlag: true}) + cmd.Annotations[flagutil.AnnotationWholeBodyFlag] = "body" + if err := flagutil.AnnotateBodyFields(cmd, unblockCmdMeta, "Body", "body"); err != nil { + return fmt.Errorf("annotate body fields for unblock: %w", err) + } + cmd.Flags().Bool("schema", false, "Print the exact JSON Schema of the request body and exit") + _ = flagutil.AnnotatePromptFlag(cmd, "schema", flagutil.PromptFlagSpec{Kind: "bool", DocSurface: true}) + if err := flagutil.DeclarePositionalFlag(cmd, "id", "Either external ID or the ID assigned by moderation API. (or pass it as the [id] argument)", true); err != nil { + return err + } + if err := interactive.Declare(cmd, interactive.CommandSpec{Args: []interactive.ArgSpec{ + {Name: "id", Summary: "Either external ID or the ID assigned by moderation API.", Required: true, SatisfiedBy: []string{"id"}}, + }}); err != nil { + return fmt.Errorf("declare interactive arguments for unblock: %w", err) + } + parent.AddCommand(cmd) + return nil +} + +// runUnblockCmd executes the unblock command. +func runUnblockCmd(cmd *cobra.Command, args []string) error { + if usage.UsageRequested(cmd) { + return usage.EmitSchema(cmd, cmd.OutOrStdout()) + } + if requested, _ := cmd.Flags().GetBool("schema"); requested { + return usage.EmitBodySchema(cmd.OutOrStdout(), "unblockAuthor") + } + if err := flagutil.ResolvePositionalFlag(cmd, args); err != nil { + return err + } + req, err := flagutil.BuildRequest[operations.UnblockAuthorRequest](cmd, unblockCmdMeta, "Body", "body") + if err != nil { + return flagutil.WithCLIValidation(err) + } + s, err := client.NewClient(cmd) + if err != nil { + return err + } + sdkOpts, err := output.PrepareCallOpts(cmd) + if err != nil { + return err + } + // Dry-run: force skip deserialization so the synthetic empty response + // does not cause parse failures in typed response handling. + if client.IsDryRun(cmd) { + sdkOpts = append(sdkOpts, operations.WithSkipDeserialization()) + } + if err := output.ValidateGlobalServerIndex(cmd, len(sdk.ServerList)); err != nil { + return err + } + if output.WantsRawJSON(cmd) { + sdkOpts = append(sdkOpts, operations.WithSkipDeserialization()) + } + stopProgress := output.StartRequestProgress(cmd) + defer stopProgress() + res, err := s.Authors.Unblock(cmd.Context(), *req, sdkOpts...) + stopProgress() + if err != nil { + return output.Error(cmd, err) + } + + if err := output.Result(cmd, res); err != nil { + return err + } + return nil +} diff --git a/internal/cli/content/allow.go b/internal/cli/content/allow.go new file mode 100644 index 0000000..363b8e6 --- /dev/null +++ b/internal/cli/content/allow.go @@ -0,0 +1,109 @@ +// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + +package content + +import ( + "fmt" + "github.com/moderation-api/cli/internal/client" + "github.com/moderation-api/cli/internal/flagutil" + "github.com/moderation-api/cli/internal/interactive" + "github.com/moderation-api/cli/internal/output" + "github.com/moderation-api/cli/internal/sdk" + "github.com/moderation-api/cli/internal/sdk/models/operations" + "github.com/moderation-api/cli/internal/usage" + "github.com/spf13/cobra" +) + +var allowCmdMeta = []flagutil.FlagMeta{ + {FlagName: "id", Shorthand: "i", FieldPath: "ID", Kind: flagutil.FlagKindString, Required: true, Description: "The content ID you sent when you submitted the content, or the ID Moderation API assigned. [required]"}, + {FlagName: "project-id", Shorthand: "p", FieldPath: "ProjectID", Kind: flagutil.FlagKindString, Optional: true, Description: "The project to act on, by ID or name. Required with an organization key unless the key reaches only one project; list projects with GET /projects. A project key acts on its own project and needs no value: naming another one is an error."}, + {FlagName: "reason", Shorthand: "r", FieldPath: "Body.Reason", Kind: flagutil.FlagKindString, Optional: true, Description: "Why the action was taken. Saved with the action and sent in the webhook event."}, + {FlagName: "queue-id", FieldPath: "Body.QueueID", Kind: flagutil.FlagKindString, Optional: true, Description: "The queue the content is reviewed in. Defaults to the project's default queue."}, + {FlagName: "moderator", Shorthand: "m", FieldPath: "Body.Moderator", Kind: flagutil.FlagKindString, Optional: true, Description: "The user ID or email of the organization member who made the decision. The action is recorded as theirs."}, + {FlagName: "create-casebook-case", Shorthand: "c", FieldPath: "Body.CreateCasebookCase", Kind: flagutil.FlagKindBool, Optional: true, Description: "Record an allow or reject decision as a casebook case. Defaults to true when 'moderator' is set, else false. Without 'moderator', the case is recorded as System."}, +} + +// initAllowCmd initializes the allow command. +func initAllowCmd(parent *cobra.Command) error { + var cmd = &cobra.Command{ + Use: "allow [id]", + Short: "Allow content", + Long: "Allow a content item with the built-in allow action, and resolve it in the review queue.", + Example: " modapi content allow --id ", + Args: flagutil.PositionalFlagArgs, + RunE: runAllowCmd, + Annotations: map[string]string{ + "speakeasy_operation": "allowContent", + }, + } + flagutil.RegisterFlags(cmd, allowCmdMeta) + if err := flagutil.ValidateMeta[operations.AllowContentRequest](allowCmdMeta); err != nil { + return fmt.Errorf("invalid metadata for allow: %w", err) + } + cmd.Flags().String("body", "", "Request body as JSON (alternative to individual flags). Can also be provided via stdin; @path reads a file, @- reads stdin to EOF. Use --schema to print the exact JSON Schema.") + _ = flagutil.AnnotatePromptFlag(cmd, "body", flagutil.PromptFlagSpec{Required: false, Kind: "json", BodyFlag: true}) + cmd.Annotations[flagutil.AnnotationWholeBodyFlag] = "body" + if err := flagutil.AnnotateBodyFields(cmd, allowCmdMeta, "Body", "body"); err != nil { + return fmt.Errorf("annotate body fields for allow: %w", err) + } + cmd.Flags().Bool("schema", false, "Print the exact JSON Schema of the request body and exit") + _ = flagutil.AnnotatePromptFlag(cmd, "schema", flagutil.PromptFlagSpec{Kind: "bool", DocSurface: true}) + if err := flagutil.DeclarePositionalFlag(cmd, "id", "The content ID you sent when you submitted the content, or the ID Moderation API assigned. (or pass it as the [id] argument)", true); err != nil { + return err + } + if err := interactive.Declare(cmd, interactive.CommandSpec{Args: []interactive.ArgSpec{ + {Name: "id", Summary: "The content ID you sent when you submitted the content, or the ID Moderation API assigned.", Required: true, SatisfiedBy: []string{"id"}}, + }}); err != nil { + return fmt.Errorf("declare interactive arguments for allow: %w", err) + } + parent.AddCommand(cmd) + return nil +} + +// runAllowCmd executes the allow command. +func runAllowCmd(cmd *cobra.Command, args []string) error { + if usage.UsageRequested(cmd) { + return usage.EmitSchema(cmd, cmd.OutOrStdout()) + } + if requested, _ := cmd.Flags().GetBool("schema"); requested { + return usage.EmitBodySchema(cmd.OutOrStdout(), "allowContent") + } + if err := flagutil.ResolvePositionalFlag(cmd, args); err != nil { + return err + } + req, err := flagutil.BuildRequest[operations.AllowContentRequest](cmd, allowCmdMeta, "Body", "body") + if err != nil { + return flagutil.WithCLIValidation(err) + } + s, err := client.NewClient(cmd) + if err != nil { + return err + } + sdkOpts, err := output.PrepareCallOpts(cmd) + if err != nil { + return err + } + // Dry-run: force skip deserialization so the synthetic empty response + // does not cause parse failures in typed response handling. + if client.IsDryRun(cmd) { + sdkOpts = append(sdkOpts, operations.WithSkipDeserialization()) + } + if err := output.ValidateGlobalServerIndex(cmd, len(sdk.ServerList)); err != nil { + return err + } + if output.WantsRawJSON(cmd) { + sdkOpts = append(sdkOpts, operations.WithSkipDeserialization()) + } + stopProgress := output.StartRequestProgress(cmd) + defer stopProgress() + res, err := s.Content.Allow(cmd.Context(), *req, sdkOpts...) + stopProgress() + if err != nil { + return output.Error(cmd, err) + } + + if err := output.Result(cmd, res); err != nil { + return err + } + return nil +} diff --git a/internal/cli/content/reject.go b/internal/cli/content/reject.go new file mode 100644 index 0000000..cdc5b70 --- /dev/null +++ b/internal/cli/content/reject.go @@ -0,0 +1,109 @@ +// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + +package content + +import ( + "fmt" + "github.com/moderation-api/cli/internal/client" + "github.com/moderation-api/cli/internal/flagutil" + "github.com/moderation-api/cli/internal/interactive" + "github.com/moderation-api/cli/internal/output" + "github.com/moderation-api/cli/internal/sdk" + "github.com/moderation-api/cli/internal/sdk/models/operations" + "github.com/moderation-api/cli/internal/usage" + "github.com/spf13/cobra" +) + +var rejectCmdMeta = []flagutil.FlagMeta{ + {FlagName: "id", Shorthand: "i", FieldPath: "ID", Kind: flagutil.FlagKindString, Required: true, Description: "The content ID you sent when you submitted the content, or the ID Moderation API assigned. [required]"}, + {FlagName: "project-id", Shorthand: "p", FieldPath: "ProjectID", Kind: flagutil.FlagKindString, Optional: true, Description: "The project to act on, by ID or name. Required with an organization key unless the key reaches only one project; list projects with GET /projects. A project key acts on its own project and needs no value: naming another one is an error."}, + {FlagName: "reason", Shorthand: "r", FieldPath: "Body.Reason", Kind: flagutil.FlagKindString, Optional: true, Description: "Why the action was taken. Saved with the action and sent in the webhook event."}, + {FlagName: "queue-id", FieldPath: "Body.QueueID", Kind: flagutil.FlagKindString, Optional: true, Description: "The queue the content is reviewed in. Defaults to the project's default queue."}, + {FlagName: "moderator", Shorthand: "m", FieldPath: "Body.Moderator", Kind: flagutil.FlagKindString, Optional: true, Description: "The user ID or email of the organization member who made the decision. The action is recorded as theirs."}, + {FlagName: "create-casebook-case", Shorthand: "c", FieldPath: "Body.CreateCasebookCase", Kind: flagutil.FlagKindBool, Optional: true, Description: "Record an allow or reject decision as a casebook case. Defaults to true when 'moderator' is set, else false. Without 'moderator', the case is recorded as System."}, +} + +// initRejectCmd initializes the reject command. +func initRejectCmd(parent *cobra.Command) error { + var cmd = &cobra.Command{ + Use: "reject [id]", + Short: "Reject content", + Long: "Reject a content item with the built-in reject action, and resolve it in the review queue.", + Example: " modapi content reject --id ", + Args: flagutil.PositionalFlagArgs, + RunE: runRejectCmd, + Annotations: map[string]string{ + "speakeasy_operation": "rejectContent", + }, + } + flagutil.RegisterFlags(cmd, rejectCmdMeta) + if err := flagutil.ValidateMeta[operations.RejectContentRequest](rejectCmdMeta); err != nil { + return fmt.Errorf("invalid metadata for reject: %w", err) + } + cmd.Flags().String("body", "", "Request body as JSON (alternative to individual flags). Can also be provided via stdin; @path reads a file, @- reads stdin to EOF. Use --schema to print the exact JSON Schema.") + _ = flagutil.AnnotatePromptFlag(cmd, "body", flagutil.PromptFlagSpec{Required: false, Kind: "json", BodyFlag: true}) + cmd.Annotations[flagutil.AnnotationWholeBodyFlag] = "body" + if err := flagutil.AnnotateBodyFields(cmd, rejectCmdMeta, "Body", "body"); err != nil { + return fmt.Errorf("annotate body fields for reject: %w", err) + } + cmd.Flags().Bool("schema", false, "Print the exact JSON Schema of the request body and exit") + _ = flagutil.AnnotatePromptFlag(cmd, "schema", flagutil.PromptFlagSpec{Kind: "bool", DocSurface: true}) + if err := flagutil.DeclarePositionalFlag(cmd, "id", "The content ID you sent when you submitted the content, or the ID Moderation API assigned. (or pass it as the [id] argument)", true); err != nil { + return err + } + if err := interactive.Declare(cmd, interactive.CommandSpec{Args: []interactive.ArgSpec{ + {Name: "id", Summary: "The content ID you sent when you submitted the content, or the ID Moderation API assigned.", Required: true, SatisfiedBy: []string{"id"}}, + }}); err != nil { + return fmt.Errorf("declare interactive arguments for reject: %w", err) + } + parent.AddCommand(cmd) + return nil +} + +// runRejectCmd executes the reject command. +func runRejectCmd(cmd *cobra.Command, args []string) error { + if usage.UsageRequested(cmd) { + return usage.EmitSchema(cmd, cmd.OutOrStdout()) + } + if requested, _ := cmd.Flags().GetBool("schema"); requested { + return usage.EmitBodySchema(cmd.OutOrStdout(), "rejectContent") + } + if err := flagutil.ResolvePositionalFlag(cmd, args); err != nil { + return err + } + req, err := flagutil.BuildRequest[operations.RejectContentRequest](cmd, rejectCmdMeta, "Body", "body") + if err != nil { + return flagutil.WithCLIValidation(err) + } + s, err := client.NewClient(cmd) + if err != nil { + return err + } + sdkOpts, err := output.PrepareCallOpts(cmd) + if err != nil { + return err + } + // Dry-run: force skip deserialization so the synthetic empty response + // does not cause parse failures in typed response handling. + if client.IsDryRun(cmd) { + sdkOpts = append(sdkOpts, operations.WithSkipDeserialization()) + } + if err := output.ValidateGlobalServerIndex(cmd, len(sdk.ServerList)); err != nil { + return err + } + if output.WantsRawJSON(cmd) { + sdkOpts = append(sdkOpts, operations.WithSkipDeserialization()) + } + stopProgress := output.StartRequestProgress(cmd) + defer stopProgress() + res, err := s.Content.Reject(cmd.Context(), *req, sdkOpts...) + stopProgress() + if err != nil { + return output.Error(cmd, err) + } + + if err := output.Result(cmd, res); err != nil { + return err + } + return nil +} diff --git a/internal/cli/content/root.go b/internal/cli/content/root.go index 37c930d..aed01f5 100644 --- a/internal/cli/content/root.go +++ b/internal/cli/content/root.go @@ -22,6 +22,14 @@ func InitContentRoot(parent *cobra.Command) error { }, } + if err := initRejectCmd(ContentCmd); err != nil { + return err + } + + if err := initAllowCmd(ContentCmd); err != nil { + return err + } + if err := initSubmitCmd(ContentCmd); err != nil { return err } diff --git a/internal/cli/root.go b/internal/cli/root.go index 16518de..5b8a910 100644 --- a/internal/cli/root.go +++ b/internal/cli/root.go @@ -100,6 +100,9 @@ func NewRootCommand() (*cobra.Command, error) { if err := actions.InitActionsRoot(rootCmd); err != nil { return nil, fmt.Errorf("init actions: %w", err) } + if err := content.InitContentRoot(rootCmd); err != nil { + return nil, fmt.Errorf("init content: %w", err) + } if err := webhooks.InitWebhooksRoot(rootCmd); err != nil { return nil, fmt.Errorf("init webhooks: %w", err) } @@ -112,9 +115,6 @@ func NewRootCommand() (*cobra.Command, error) { if err := wordlist.InitWordlistRoot(rootCmd); err != nil { return nil, fmt.Errorf("init wordlist: %w", err) } - if err := content.InitContentRoot(rootCmd); err != nil { - return nil, fmt.Errorf("init content: %w", err) - } if err := projects.InitProjectsRoot(rootCmd); err != nil { return nil, fmt.Errorf("init projects: %w", err) } diff --git a/internal/cli/version.go b/internal/cli/version.go index 35c3cf4..c1573ad 100644 --- a/internal/cli/version.go +++ b/internal/cli/version.go @@ -14,7 +14,7 @@ import ( // which propagates the value here (see cmd/modapi/main.go): // // go build -ldflags "-X main.version=x.y.z" ./cmd/modapi -var Version = "0.5.0" +var Version = "0.6.0" // BuildTime is optionally set at build time via ldflags targeting the main package. var BuildTime string diff --git a/internal/sdk/authors.go b/internal/sdk/authors.go index 9a2dac7..ea34a70 100644 --- a/internal/sdk/authors.go +++ b/internal/sdk/authors.go @@ -1623,3 +1623,795 @@ func (s *Authors) Delete(ctx context.Context, request operations.DeleteAuthorReq return res, nil } + +// Block author +// Block an author with the built-in block action. A blocked author cannot post new content until unblocked. +func (s *Authors) Block(ctx context.Context, request operations.BlockAuthorRequest, opts ...operations.Option) (*operations.BlockAuthorResponse, error) { + o := operations.Options{} + supportedOptions := []string{ + operations.SupportedOptionTimeout, + operations.SupportedOptionSkipDeserialization, + } + + for _, opt := range opts { + if err := opt(&o, supportedOptions...); err != nil { + return nil, fmt.Errorf("error applying option: %w", err) + } + } + + var baseURL string + if o.ServerURL == nil { + baseURL = utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) + } else { + baseURL = *o.ServerURL + } + opURL, err := utils.GenerateURL(ctx, baseURL, "/authors/{id}/block", request, nil) + if err != nil { + return nil, fmt.Errorf("error generating URL: %w", err) + } + + hookCtx := hooks.HookContext{ + SDK: s.rootSDK, + SDKConfiguration: s.sdkConfiguration, + BaseURL: baseURL, + Context: ctx, + OperationID: "blockAuthor", + SecuritySource: s.sdkConfiguration.Security, + } + bodyReader, reqContentType, err := utils.SerializeRequestBody(ctx, request, false, false, "Body", "json", `request:"mediaType=application/json"`) + if err != nil { + return nil, err + } + + timeout := o.Timeout + if timeout == nil { + timeout = s.sdkConfiguration.Timeout + } + + var streamCancel context.CancelFunc + + if timeout != nil { + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(ctx, *timeout) + streamCancel = cancel + defer func() { + if streamCancel != nil { + streamCancel() + } + }() + } + + req, err := http.NewRequestWithContext(ctx, "POST", opURL, bodyReader) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + req.Header.Set("Accept", "application/json") + req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) + if reqContentType != "" { + req.Header.Set("Content-Type", reqContentType) + } + + utils.PopulateHeaders(ctx, req, request, nil) + + if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { + return nil, err + } + + for k, v := range o.SetHeaders { + req.Header.Set(k, v) + } + + req, err = s.hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) + if err != nil { + return nil, err + } + + httpRes, err := s.sdkConfiguration.Client.Do(req) + if err != nil || httpRes == nil { + if err != nil { + err = fmt.Errorf("error sending request: %w", err) + } else { + err = fmt.Errorf("error sending request: no response") + } + + _, err = s.hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) + return nil, err + } else if utils.MatchStatusCodes([]string{"4XX", "5XX"}, httpRes.StatusCode) { + _httpRes, err := s.hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) + if err != nil { + return nil, err + } else if _httpRes != nil { + httpRes = _httpRes + } + } else { + httpRes, err = s.hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) + if err != nil { + return nil, err + } + } + + res := &operations.BlockAuthorResponse{ + HTTPMeta: components.HTTPMetadata{ + Request: req, + Response: httpRes, + }, + } + + switch { + case httpRes.StatusCode == 200: + switch { + case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): + if o.SkipDeserialization != nil && *o.SkipDeserialization { + httpRes.Body = utils.BodyWithCancel(httpRes.Body, streamCancel) + streamCancel = nil + } else { + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + + var out operations.BlockAuthorResponseBody + if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { + return nil, err + } + + res.Object = &out + } + default: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, sdkerrors.NewSDKDefaultError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) + } + case httpRes.StatusCode == 400: + switch { + case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + + var out sdkerrors.BadRequestError + if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { + return nil, sdkerrors.NewResponseValidationError("response did not match the declared error schema", httpRes.StatusCode, string(rawBody), httpRes, err) + } + + out.HTTPMeta = components.HTTPMetadata{ + Request: req, + Response: httpRes, + } + return nil, &out + default: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, sdkerrors.NewSDKDefaultError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) + } + case httpRes.StatusCode == 401: + switch { + case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + + var out sdkerrors.AuthenticationError + if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { + return nil, sdkerrors.NewResponseValidationError("response did not match the declared error schema", httpRes.StatusCode, string(rawBody), httpRes, err) + } + + out.HTTPMeta = components.HTTPMetadata{ + Request: req, + Response: httpRes, + } + return nil, &out + default: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, sdkerrors.NewSDKDefaultError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) + } + case httpRes.StatusCode == 404: + switch { + case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + + var out sdkerrors.NotFoundError + if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { + return nil, sdkerrors.NewResponseValidationError("response did not match the declared error schema", httpRes.StatusCode, string(rawBody), httpRes, err) + } + + out.HTTPMeta = components.HTTPMetadata{ + Request: req, + Response: httpRes, + } + return nil, &out + default: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, sdkerrors.NewSDKDefaultError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) + } + case httpRes.StatusCode == 429: + switch { + case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + + var out sdkerrors.RateLimitError + if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { + return nil, sdkerrors.NewResponseValidationError("response did not match the declared error schema", httpRes.StatusCode, string(rawBody), httpRes, err) + } + + out.HTTPMeta = components.HTTPMetadata{ + Request: req, + Response: httpRes, + } + return nil, &out + default: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, sdkerrors.NewSDKDefaultError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) + } + case httpRes.StatusCode >= 400 && httpRes.StatusCode < 500: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, sdkerrors.NewSDKDefaultError("API error occurred", httpRes.StatusCode, string(rawBody), httpRes) + case httpRes.StatusCode >= 500 && httpRes.StatusCode < 600: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, sdkerrors.NewSDKDefaultError("API error occurred", httpRes.StatusCode, string(rawBody), httpRes) + default: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, sdkerrors.NewSDKDefaultError("unknown status code returned", httpRes.StatusCode, string(rawBody), httpRes) + } + + return res, nil + +} + +// Suspend author +// Block an author for a limited time with the built-in suspend action. +func (s *Authors) Suspend(ctx context.Context, request operations.SuspendAuthorRequest, opts ...operations.Option) (*operations.SuspendAuthorResponse, error) { + o := operations.Options{} + supportedOptions := []string{ + operations.SupportedOptionTimeout, + operations.SupportedOptionSkipDeserialization, + } + + for _, opt := range opts { + if err := opt(&o, supportedOptions...); err != nil { + return nil, fmt.Errorf("error applying option: %w", err) + } + } + + var baseURL string + if o.ServerURL == nil { + baseURL = utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) + } else { + baseURL = *o.ServerURL + } + opURL, err := utils.GenerateURL(ctx, baseURL, "/authors/{id}/suspend", request, nil) + if err != nil { + return nil, fmt.Errorf("error generating URL: %w", err) + } + + hookCtx := hooks.HookContext{ + SDK: s.rootSDK, + SDKConfiguration: s.sdkConfiguration, + BaseURL: baseURL, + Context: ctx, + OperationID: "suspendAuthor", + SecuritySource: s.sdkConfiguration.Security, + } + bodyReader, reqContentType, err := utils.SerializeRequestBody(ctx, request, false, false, "Body", "json", `request:"mediaType=application/json"`) + if err != nil { + return nil, err + } + + timeout := o.Timeout + if timeout == nil { + timeout = s.sdkConfiguration.Timeout + } + + var streamCancel context.CancelFunc + + if timeout != nil { + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(ctx, *timeout) + streamCancel = cancel + defer func() { + if streamCancel != nil { + streamCancel() + } + }() + } + + req, err := http.NewRequestWithContext(ctx, "POST", opURL, bodyReader) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + req.Header.Set("Accept", "application/json") + req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) + if reqContentType != "" { + req.Header.Set("Content-Type", reqContentType) + } + + utils.PopulateHeaders(ctx, req, request, nil) + + if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { + return nil, err + } + + for k, v := range o.SetHeaders { + req.Header.Set(k, v) + } + + req, err = s.hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) + if err != nil { + return nil, err + } + + httpRes, err := s.sdkConfiguration.Client.Do(req) + if err != nil || httpRes == nil { + if err != nil { + err = fmt.Errorf("error sending request: %w", err) + } else { + err = fmt.Errorf("error sending request: no response") + } + + _, err = s.hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) + return nil, err + } else if utils.MatchStatusCodes([]string{"4XX", "5XX"}, httpRes.StatusCode) { + _httpRes, err := s.hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) + if err != nil { + return nil, err + } else if _httpRes != nil { + httpRes = _httpRes + } + } else { + httpRes, err = s.hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) + if err != nil { + return nil, err + } + } + + res := &operations.SuspendAuthorResponse{ + HTTPMeta: components.HTTPMetadata{ + Request: req, + Response: httpRes, + }, + } + + switch { + case httpRes.StatusCode == 200: + switch { + case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): + if o.SkipDeserialization != nil && *o.SkipDeserialization { + httpRes.Body = utils.BodyWithCancel(httpRes.Body, streamCancel) + streamCancel = nil + } else { + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + + var out operations.SuspendAuthorResponseBody + if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { + return nil, err + } + + res.Object = &out + } + default: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, sdkerrors.NewSDKDefaultError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) + } + case httpRes.StatusCode == 400: + switch { + case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + + var out sdkerrors.BadRequestError + if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { + return nil, sdkerrors.NewResponseValidationError("response did not match the declared error schema", httpRes.StatusCode, string(rawBody), httpRes, err) + } + + out.HTTPMeta = components.HTTPMetadata{ + Request: req, + Response: httpRes, + } + return nil, &out + default: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, sdkerrors.NewSDKDefaultError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) + } + case httpRes.StatusCode == 401: + switch { + case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + + var out sdkerrors.AuthenticationError + if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { + return nil, sdkerrors.NewResponseValidationError("response did not match the declared error schema", httpRes.StatusCode, string(rawBody), httpRes, err) + } + + out.HTTPMeta = components.HTTPMetadata{ + Request: req, + Response: httpRes, + } + return nil, &out + default: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, sdkerrors.NewSDKDefaultError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) + } + case httpRes.StatusCode == 404: + switch { + case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + + var out sdkerrors.NotFoundError + if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { + return nil, sdkerrors.NewResponseValidationError("response did not match the declared error schema", httpRes.StatusCode, string(rawBody), httpRes, err) + } + + out.HTTPMeta = components.HTTPMetadata{ + Request: req, + Response: httpRes, + } + return nil, &out + default: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, sdkerrors.NewSDKDefaultError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) + } + case httpRes.StatusCode == 429: + switch { + case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + + var out sdkerrors.RateLimitError + if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { + return nil, sdkerrors.NewResponseValidationError("response did not match the declared error schema", httpRes.StatusCode, string(rawBody), httpRes, err) + } + + out.HTTPMeta = components.HTTPMetadata{ + Request: req, + Response: httpRes, + } + return nil, &out + default: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, sdkerrors.NewSDKDefaultError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) + } + case httpRes.StatusCode >= 400 && httpRes.StatusCode < 500: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, sdkerrors.NewSDKDefaultError("API error occurred", httpRes.StatusCode, string(rawBody), httpRes) + case httpRes.StatusCode >= 500 && httpRes.StatusCode < 600: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, sdkerrors.NewSDKDefaultError("API error occurred", httpRes.StatusCode, string(rawBody), httpRes) + default: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, sdkerrors.NewSDKDefaultError("unknown status code returned", httpRes.StatusCode, string(rawBody), httpRes) + } + + return res, nil + +} + +// Unblock author +// Unblock a blocked or suspended author with the built-in unblock action, so they can post new content again. +func (s *Authors) Unblock(ctx context.Context, request operations.UnblockAuthorRequest, opts ...operations.Option) (*operations.UnblockAuthorResponse, error) { + o := operations.Options{} + supportedOptions := []string{ + operations.SupportedOptionTimeout, + operations.SupportedOptionSkipDeserialization, + } + + for _, opt := range opts { + if err := opt(&o, supportedOptions...); err != nil { + return nil, fmt.Errorf("error applying option: %w", err) + } + } + + var baseURL string + if o.ServerURL == nil { + baseURL = utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) + } else { + baseURL = *o.ServerURL + } + opURL, err := utils.GenerateURL(ctx, baseURL, "/authors/{id}/unblock", request, nil) + if err != nil { + return nil, fmt.Errorf("error generating URL: %w", err) + } + + hookCtx := hooks.HookContext{ + SDK: s.rootSDK, + SDKConfiguration: s.sdkConfiguration, + BaseURL: baseURL, + Context: ctx, + OperationID: "unblockAuthor", + SecuritySource: s.sdkConfiguration.Security, + } + bodyReader, reqContentType, err := utils.SerializeRequestBody(ctx, request, false, false, "Body", "json", `request:"mediaType=application/json"`) + if err != nil { + return nil, err + } + + timeout := o.Timeout + if timeout == nil { + timeout = s.sdkConfiguration.Timeout + } + + var streamCancel context.CancelFunc + + if timeout != nil { + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(ctx, *timeout) + streamCancel = cancel + defer func() { + if streamCancel != nil { + streamCancel() + } + }() + } + + req, err := http.NewRequestWithContext(ctx, "POST", opURL, bodyReader) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + req.Header.Set("Accept", "application/json") + req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) + if reqContentType != "" { + req.Header.Set("Content-Type", reqContentType) + } + + utils.PopulateHeaders(ctx, req, request, nil) + + if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { + return nil, err + } + + for k, v := range o.SetHeaders { + req.Header.Set(k, v) + } + + req, err = s.hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) + if err != nil { + return nil, err + } + + httpRes, err := s.sdkConfiguration.Client.Do(req) + if err != nil || httpRes == nil { + if err != nil { + err = fmt.Errorf("error sending request: %w", err) + } else { + err = fmt.Errorf("error sending request: no response") + } + + _, err = s.hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) + return nil, err + } else if utils.MatchStatusCodes([]string{"4XX", "5XX"}, httpRes.StatusCode) { + _httpRes, err := s.hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) + if err != nil { + return nil, err + } else if _httpRes != nil { + httpRes = _httpRes + } + } else { + httpRes, err = s.hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) + if err != nil { + return nil, err + } + } + + res := &operations.UnblockAuthorResponse{ + HTTPMeta: components.HTTPMetadata{ + Request: req, + Response: httpRes, + }, + } + + switch { + case httpRes.StatusCode == 200: + switch { + case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): + if o.SkipDeserialization != nil && *o.SkipDeserialization { + httpRes.Body = utils.BodyWithCancel(httpRes.Body, streamCancel) + streamCancel = nil + } else { + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + + var out operations.UnblockAuthorResponseBody + if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { + return nil, err + } + + res.Object = &out + } + default: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, sdkerrors.NewSDKDefaultError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) + } + case httpRes.StatusCode == 400: + switch { + case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + + var out sdkerrors.BadRequestError + if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { + return nil, sdkerrors.NewResponseValidationError("response did not match the declared error schema", httpRes.StatusCode, string(rawBody), httpRes, err) + } + + out.HTTPMeta = components.HTTPMetadata{ + Request: req, + Response: httpRes, + } + return nil, &out + default: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, sdkerrors.NewSDKDefaultError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) + } + case httpRes.StatusCode == 401: + switch { + case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + + var out sdkerrors.AuthenticationError + if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { + return nil, sdkerrors.NewResponseValidationError("response did not match the declared error schema", httpRes.StatusCode, string(rawBody), httpRes, err) + } + + out.HTTPMeta = components.HTTPMetadata{ + Request: req, + Response: httpRes, + } + return nil, &out + default: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, sdkerrors.NewSDKDefaultError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) + } + case httpRes.StatusCode == 404: + switch { + case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + + var out sdkerrors.NotFoundError + if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { + return nil, sdkerrors.NewResponseValidationError("response did not match the declared error schema", httpRes.StatusCode, string(rawBody), httpRes, err) + } + + out.HTTPMeta = components.HTTPMetadata{ + Request: req, + Response: httpRes, + } + return nil, &out + default: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, sdkerrors.NewSDKDefaultError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) + } + case httpRes.StatusCode == 429: + switch { + case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + + var out sdkerrors.RateLimitError + if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { + return nil, sdkerrors.NewResponseValidationError("response did not match the declared error schema", httpRes.StatusCode, string(rawBody), httpRes, err) + } + + out.HTTPMeta = components.HTTPMetadata{ + Request: req, + Response: httpRes, + } + return nil, &out + default: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, sdkerrors.NewSDKDefaultError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) + } + case httpRes.StatusCode >= 400 && httpRes.StatusCode < 500: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, sdkerrors.NewSDKDefaultError("API error occurred", httpRes.StatusCode, string(rawBody), httpRes) + case httpRes.StatusCode >= 500 && httpRes.StatusCode < 600: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, sdkerrors.NewSDKDefaultError("API error occurred", httpRes.StatusCode, string(rawBody), httpRes) + default: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, sdkerrors.NewSDKDefaultError("unknown status code returned", httpRes.StatusCode, string(rawBody), httpRes) + } + + return res, nil + +} diff --git a/internal/sdk/content.go b/internal/sdk/content.go index 8a91f5f..cc77d44 100644 --- a/internal/sdk/content.go +++ b/internal/sdk/content.go @@ -30,6 +30,534 @@ func newContent(rootSDK *ModerationAPI, sdkConfig config.SDKConfiguration, hooks } } +// Reject content +// Reject a content item with the built-in reject action, and resolve it in the review queue. +func (s *Content) Reject(ctx context.Context, request operations.RejectContentRequest, opts ...operations.Option) (*operations.RejectContentResponse, error) { + o := operations.Options{} + supportedOptions := []string{ + operations.SupportedOptionTimeout, + operations.SupportedOptionSkipDeserialization, + } + + for _, opt := range opts { + if err := opt(&o, supportedOptions...); err != nil { + return nil, fmt.Errorf("error applying option: %w", err) + } + } + + var baseURL string + if o.ServerURL == nil { + baseURL = utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) + } else { + baseURL = *o.ServerURL + } + opURL, err := utils.GenerateURL(ctx, baseURL, "/content/{id}/reject", request, nil) + if err != nil { + return nil, fmt.Errorf("error generating URL: %w", err) + } + + hookCtx := hooks.HookContext{ + SDK: s.rootSDK, + SDKConfiguration: s.sdkConfiguration, + BaseURL: baseURL, + Context: ctx, + OperationID: "rejectContent", + SecuritySource: s.sdkConfiguration.Security, + } + bodyReader, reqContentType, err := utils.SerializeRequestBody(ctx, request, false, false, "Body", "json", `request:"mediaType=application/json"`) + if err != nil { + return nil, err + } + + timeout := o.Timeout + if timeout == nil { + timeout = s.sdkConfiguration.Timeout + } + + var streamCancel context.CancelFunc + + if timeout != nil { + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(ctx, *timeout) + streamCancel = cancel + defer func() { + if streamCancel != nil { + streamCancel() + } + }() + } + + req, err := http.NewRequestWithContext(ctx, "POST", opURL, bodyReader) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + req.Header.Set("Accept", "application/json") + req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) + if reqContentType != "" { + req.Header.Set("Content-Type", reqContentType) + } + + utils.PopulateHeaders(ctx, req, request, nil) + + if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { + return nil, err + } + + for k, v := range o.SetHeaders { + req.Header.Set(k, v) + } + + req, err = s.hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) + if err != nil { + return nil, err + } + + httpRes, err := s.sdkConfiguration.Client.Do(req) + if err != nil || httpRes == nil { + if err != nil { + err = fmt.Errorf("error sending request: %w", err) + } else { + err = fmt.Errorf("error sending request: no response") + } + + _, err = s.hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) + return nil, err + } else if utils.MatchStatusCodes([]string{"4XX", "5XX"}, httpRes.StatusCode) { + _httpRes, err := s.hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) + if err != nil { + return nil, err + } else if _httpRes != nil { + httpRes = _httpRes + } + } else { + httpRes, err = s.hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) + if err != nil { + return nil, err + } + } + + res := &operations.RejectContentResponse{ + HTTPMeta: components.HTTPMetadata{ + Request: req, + Response: httpRes, + }, + } + + switch { + case httpRes.StatusCode == 200: + switch { + case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): + if o.SkipDeserialization != nil && *o.SkipDeserialization { + httpRes.Body = utils.BodyWithCancel(httpRes.Body, streamCancel) + streamCancel = nil + } else { + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + + var out operations.RejectContentResponseBody + if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { + return nil, err + } + + res.Object = &out + } + default: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, sdkerrors.NewSDKDefaultError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) + } + case httpRes.StatusCode == 400: + switch { + case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + + var out sdkerrors.BadRequestError + if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { + return nil, sdkerrors.NewResponseValidationError("response did not match the declared error schema", httpRes.StatusCode, string(rawBody), httpRes, err) + } + + out.HTTPMeta = components.HTTPMetadata{ + Request: req, + Response: httpRes, + } + return nil, &out + default: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, sdkerrors.NewSDKDefaultError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) + } + case httpRes.StatusCode == 401: + switch { + case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + + var out sdkerrors.AuthenticationError + if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { + return nil, sdkerrors.NewResponseValidationError("response did not match the declared error schema", httpRes.StatusCode, string(rawBody), httpRes, err) + } + + out.HTTPMeta = components.HTTPMetadata{ + Request: req, + Response: httpRes, + } + return nil, &out + default: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, sdkerrors.NewSDKDefaultError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) + } + case httpRes.StatusCode == 404: + switch { + case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + + var out sdkerrors.NotFoundError + if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { + return nil, sdkerrors.NewResponseValidationError("response did not match the declared error schema", httpRes.StatusCode, string(rawBody), httpRes, err) + } + + out.HTTPMeta = components.HTTPMetadata{ + Request: req, + Response: httpRes, + } + return nil, &out + default: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, sdkerrors.NewSDKDefaultError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) + } + case httpRes.StatusCode == 429: + switch { + case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + + var out sdkerrors.RateLimitError + if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { + return nil, sdkerrors.NewResponseValidationError("response did not match the declared error schema", httpRes.StatusCode, string(rawBody), httpRes, err) + } + + out.HTTPMeta = components.HTTPMetadata{ + Request: req, + Response: httpRes, + } + return nil, &out + default: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, sdkerrors.NewSDKDefaultError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) + } + case httpRes.StatusCode >= 400 && httpRes.StatusCode < 500: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, sdkerrors.NewSDKDefaultError("API error occurred", httpRes.StatusCode, string(rawBody), httpRes) + case httpRes.StatusCode >= 500 && httpRes.StatusCode < 600: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, sdkerrors.NewSDKDefaultError("API error occurred", httpRes.StatusCode, string(rawBody), httpRes) + default: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, sdkerrors.NewSDKDefaultError("unknown status code returned", httpRes.StatusCode, string(rawBody), httpRes) + } + + return res, nil + +} + +// Allow content +// Allow a content item with the built-in allow action, and resolve it in the review queue. +func (s *Content) Allow(ctx context.Context, request operations.AllowContentRequest, opts ...operations.Option) (*operations.AllowContentResponse, error) { + o := operations.Options{} + supportedOptions := []string{ + operations.SupportedOptionTimeout, + operations.SupportedOptionSkipDeserialization, + } + + for _, opt := range opts { + if err := opt(&o, supportedOptions...); err != nil { + return nil, fmt.Errorf("error applying option: %w", err) + } + } + + var baseURL string + if o.ServerURL == nil { + baseURL = utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) + } else { + baseURL = *o.ServerURL + } + opURL, err := utils.GenerateURL(ctx, baseURL, "/content/{id}/allow", request, nil) + if err != nil { + return nil, fmt.Errorf("error generating URL: %w", err) + } + + hookCtx := hooks.HookContext{ + SDK: s.rootSDK, + SDKConfiguration: s.sdkConfiguration, + BaseURL: baseURL, + Context: ctx, + OperationID: "allowContent", + SecuritySource: s.sdkConfiguration.Security, + } + bodyReader, reqContentType, err := utils.SerializeRequestBody(ctx, request, false, false, "Body", "json", `request:"mediaType=application/json"`) + if err != nil { + return nil, err + } + + timeout := o.Timeout + if timeout == nil { + timeout = s.sdkConfiguration.Timeout + } + + var streamCancel context.CancelFunc + + if timeout != nil { + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(ctx, *timeout) + streamCancel = cancel + defer func() { + if streamCancel != nil { + streamCancel() + } + }() + } + + req, err := http.NewRequestWithContext(ctx, "POST", opURL, bodyReader) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + req.Header.Set("Accept", "application/json") + req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) + if reqContentType != "" { + req.Header.Set("Content-Type", reqContentType) + } + + utils.PopulateHeaders(ctx, req, request, nil) + + if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { + return nil, err + } + + for k, v := range o.SetHeaders { + req.Header.Set(k, v) + } + + req, err = s.hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) + if err != nil { + return nil, err + } + + httpRes, err := s.sdkConfiguration.Client.Do(req) + if err != nil || httpRes == nil { + if err != nil { + err = fmt.Errorf("error sending request: %w", err) + } else { + err = fmt.Errorf("error sending request: no response") + } + + _, err = s.hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) + return nil, err + } else if utils.MatchStatusCodes([]string{"4XX", "5XX"}, httpRes.StatusCode) { + _httpRes, err := s.hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) + if err != nil { + return nil, err + } else if _httpRes != nil { + httpRes = _httpRes + } + } else { + httpRes, err = s.hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) + if err != nil { + return nil, err + } + } + + res := &operations.AllowContentResponse{ + HTTPMeta: components.HTTPMetadata{ + Request: req, + Response: httpRes, + }, + } + + switch { + case httpRes.StatusCode == 200: + switch { + case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): + if o.SkipDeserialization != nil && *o.SkipDeserialization { + httpRes.Body = utils.BodyWithCancel(httpRes.Body, streamCancel) + streamCancel = nil + } else { + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + + var out operations.AllowContentResponseBody + if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { + return nil, err + } + + res.Object = &out + } + default: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, sdkerrors.NewSDKDefaultError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) + } + case httpRes.StatusCode == 400: + switch { + case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + + var out sdkerrors.BadRequestError + if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { + return nil, sdkerrors.NewResponseValidationError("response did not match the declared error schema", httpRes.StatusCode, string(rawBody), httpRes, err) + } + + out.HTTPMeta = components.HTTPMetadata{ + Request: req, + Response: httpRes, + } + return nil, &out + default: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, sdkerrors.NewSDKDefaultError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) + } + case httpRes.StatusCode == 401: + switch { + case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + + var out sdkerrors.AuthenticationError + if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { + return nil, sdkerrors.NewResponseValidationError("response did not match the declared error schema", httpRes.StatusCode, string(rawBody), httpRes, err) + } + + out.HTTPMeta = components.HTTPMetadata{ + Request: req, + Response: httpRes, + } + return nil, &out + default: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, sdkerrors.NewSDKDefaultError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) + } + case httpRes.StatusCode == 404: + switch { + case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + + var out sdkerrors.NotFoundError + if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { + return nil, sdkerrors.NewResponseValidationError("response did not match the declared error schema", httpRes.StatusCode, string(rawBody), httpRes, err) + } + + out.HTTPMeta = components.HTTPMetadata{ + Request: req, + Response: httpRes, + } + return nil, &out + default: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, sdkerrors.NewSDKDefaultError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) + } + case httpRes.StatusCode == 429: + switch { + case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + + var out sdkerrors.RateLimitError + if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { + return nil, sdkerrors.NewResponseValidationError("response did not match the declared error schema", httpRes.StatusCode, string(rawBody), httpRes, err) + } + + out.HTTPMeta = components.HTTPMetadata{ + Request: req, + Response: httpRes, + } + return nil, &out + default: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, sdkerrors.NewSDKDefaultError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) + } + case httpRes.StatusCode >= 400 && httpRes.StatusCode < 500: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, sdkerrors.NewSDKDefaultError("API error occurred", httpRes.StatusCode, string(rawBody), httpRes) + case httpRes.StatusCode >= 500 && httpRes.StatusCode < 600: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, sdkerrors.NewSDKDefaultError("API error occurred", httpRes.StatusCode, string(rawBody), httpRes) + default: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, sdkerrors.NewSDKDefaultError("unknown status code returned", httpRes.StatusCode, string(rawBody), httpRes) + } + + return res, nil + +} + // Submit content func (s *Content) Submit(ctx context.Context, request operations.SubmitContentRequest, opts ...operations.Option) (*operations.SubmitContentResponse, error) { o := operations.Options{} diff --git a/internal/sdk/models/operations/allowcontent.go b/internal/sdk/models/operations/allowcontent.go new file mode 100644 index 0000000..3e327d9 --- /dev/null +++ b/internal/sdk/models/operations/allowcontent.go @@ -0,0 +1,120 @@ +// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + +package operations + +import ( + "github.com/moderation-api/cli/internal/sdk/models/components" + "github.com/moderation-api/cli/internal/sdk/sdkinternal/utils" +) + +type AllowContentRequestBody struct { + // Why the action was taken. Saved with the action and sent in the webhook event. + Reason *string `json:"reason,omitzero"` + // The queue the content is reviewed in. Defaults to the project's default queue. + QueueID *string `json:"queueId,omitzero"` + // The user ID or email of the organization member who made the decision. The action is recorded as theirs. + Moderator *string `json:"moderator,omitzero"` + // Record an allow or reject decision as a casebook case. Defaults to true when `moderator` is set, else false. Without `moderator`, the case is recorded as System. + CreateCasebookCase *bool `json:"createCasebookCase,omitzero"` +} + +func (a *AllowContentRequestBody) GetReason() *string { + if a == nil { + return nil + } + return a.Reason +} + +func (a *AllowContentRequestBody) GetQueueID() *string { + if a == nil { + return nil + } + return a.QueueID +} + +func (a *AllowContentRequestBody) GetModerator() *string { + if a == nil { + return nil + } + return a.Moderator +} + +func (a *AllowContentRequestBody) GetCreateCasebookCase() *bool { + if a == nil { + return nil + } + return a.CreateCasebookCase +} + +type AllowContentRequest struct { + // The content ID you sent when you submitted the content, or the ID Moderation API assigned. + ID string `pathParam:"style=simple,explode=false,name=id"` + // The project to act on, by ID or name. Required with an organization key unless the key reaches only one project; list projects with GET /projects. A project key acts on its own project and needs no value: naming another one is an error. + ProjectID *string `header:"style=simple,explode=false,name=Project-Id"` + Body AllowContentRequestBody `request:"mediaType=application/json"` +} + +func (a *AllowContentRequest) GetID() string { + if a == nil { + return "" + } + return a.ID +} + +func (a *AllowContentRequest) GetProjectID() *string { + if a == nil { + return nil + } + return a.ProjectID +} + +func (a *AllowContentRequest) GetBody() AllowContentRequestBody { + if a == nil { + return AllowContentRequestBody{} + } + return a.Body +} + +// AllowContentResponseBody - Execution result +type AllowContentResponseBody struct { + // Whether the action was executed successfully + Success bool `json:"success"` +} + +func (a *AllowContentResponseBody) GetSuccess() bool { + if a == nil { + return false + } + return a.Success +} + +type AllowContentResponse struct { + HTTPMeta components.HTTPMetadata `json:"-"` + // Action executed successfully + Object *AllowContentResponseBody +} + +func (a AllowContentResponse) MarshalJSON() ([]byte, error) { + return utils.MarshalJSON(a, "", false) +} + +func (a *AllowContentResponse) UnmarshalJSON(data []byte) error { + if err := utils.UnmarshalJSON(data, &a, "", false, nil); err != nil { + return err + } + return nil +} + +func (a *AllowContentResponse) GetHTTPMeta() components.HTTPMetadata { + if a == nil { + return components.HTTPMetadata{} + } + return a.HTTPMeta +} + +func (a *AllowContentResponse) GetObject() *AllowContentResponseBody { + if a == nil { + return nil + } + return a.Object +} diff --git a/internal/sdk/models/operations/blockauthor.go b/internal/sdk/models/operations/blockauthor.go new file mode 100644 index 0000000..01b02eb --- /dev/null +++ b/internal/sdk/models/operations/blockauthor.go @@ -0,0 +1,93 @@ +// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + +package operations + +import ( + "github.com/moderation-api/cli/internal/sdk/models/components" + "github.com/moderation-api/cli/internal/sdk/sdkinternal/utils" +) + +type BlockAuthorRequestBody struct { + // Why the action was taken. Saved with the action and sent in the webhook event. + Reason *string `json:"reason,omitzero"` +} + +func (b *BlockAuthorRequestBody) GetReason() *string { + if b == nil { + return nil + } + return b.Reason +} + +type BlockAuthorRequest struct { + // Either external ID or the ID assigned by moderation API. + ID string `pathParam:"style=simple,explode=false,name=id"` + // The project to act on, by ID or name. Required with an organization key unless the key reaches only one project; list projects with GET /projects. A project key acts on its own project and needs no value: naming another one is an error. + ProjectID *string `header:"style=simple,explode=false,name=Project-Id"` + Body BlockAuthorRequestBody `request:"mediaType=application/json"` +} + +func (b *BlockAuthorRequest) GetID() string { + if b == nil { + return "" + } + return b.ID +} + +func (b *BlockAuthorRequest) GetProjectID() *string { + if b == nil { + return nil + } + return b.ProjectID +} + +func (b *BlockAuthorRequest) GetBody() BlockAuthorRequestBody { + if b == nil { + return BlockAuthorRequestBody{} + } + return b.Body +} + +// BlockAuthorResponseBody - Execution result +type BlockAuthorResponseBody struct { + // Whether the action was executed successfully + Success bool `json:"success"` +} + +func (b *BlockAuthorResponseBody) GetSuccess() bool { + if b == nil { + return false + } + return b.Success +} + +type BlockAuthorResponse struct { + HTTPMeta components.HTTPMetadata `json:"-"` + // Action executed successfully + Object *BlockAuthorResponseBody +} + +func (b BlockAuthorResponse) MarshalJSON() ([]byte, error) { + return utils.MarshalJSON(b, "", false) +} + +func (b *BlockAuthorResponse) UnmarshalJSON(data []byte) error { + if err := utils.UnmarshalJSON(data, &b, "", false, nil); err != nil { + return err + } + return nil +} + +func (b *BlockAuthorResponse) GetHTTPMeta() components.HTTPMetadata { + if b == nil { + return components.HTTPMetadata{} + } + return b.HTTPMeta +} + +func (b *BlockAuthorResponse) GetObject() *BlockAuthorResponseBody { + if b == nil { + return nil + } + return b.Object +} diff --git a/internal/sdk/models/operations/executeaction.go b/internal/sdk/models/operations/executeaction.go index 58d2f9c..8ba5e5a 100644 --- a/internal/sdk/models/operations/executeaction.go +++ b/internal/sdk/models/operations/executeaction.go @@ -21,6 +21,10 @@ type ExecuteActionRequestBody struct { QueueID *string `json:"queueId,omitzero"` // Optional duration in milliseconds for actions with timeouts Duration *float64 `json:"duration,omitzero"` + // The user ID or email of the organization member who made the decision. The action is recorded as theirs. + Moderator *string `json:"moderator,omitzero"` + // Record an allow or reject decision as a casebook case. Defaults to true when `moderator` is set, else false. Without `moderator`, the case is recorded as System. + CreateCasebookCase *bool `json:"createCasebookCase,omitzero"` } func (e ExecuteActionRequestBody) MarshalJSON() ([]byte, error) { @@ -76,6 +80,20 @@ func (e *ExecuteActionRequestBody) GetDuration() *float64 { return e.Duration } +func (e *ExecuteActionRequestBody) GetModerator() *string { + if e == nil { + return nil + } + return e.Moderator +} + +func (e *ExecuteActionRequestBody) GetCreateCasebookCase() *bool { + if e == nil { + return nil + } + return e.CreateCasebookCase +} + type ExecuteActionRequest struct { // The project to act on, by ID or name. Required with an organization key unless the key reaches only one project; list projects with GET /projects. A project key acts on its own project and needs no value: naming another one is an error. ProjectID *string `header:"style=simple,explode=false,name=Project-Id"` diff --git a/internal/sdk/models/operations/rejectcontent.go b/internal/sdk/models/operations/rejectcontent.go new file mode 100644 index 0000000..11bbfaf --- /dev/null +++ b/internal/sdk/models/operations/rejectcontent.go @@ -0,0 +1,120 @@ +// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + +package operations + +import ( + "github.com/moderation-api/cli/internal/sdk/models/components" + "github.com/moderation-api/cli/internal/sdk/sdkinternal/utils" +) + +type RejectContentRequestBody struct { + // Why the action was taken. Saved with the action and sent in the webhook event. + Reason *string `json:"reason,omitzero"` + // The queue the content is reviewed in. Defaults to the project's default queue. + QueueID *string `json:"queueId,omitzero"` + // The user ID or email of the organization member who made the decision. The action is recorded as theirs. + Moderator *string `json:"moderator,omitzero"` + // Record an allow or reject decision as a casebook case. Defaults to true when `moderator` is set, else false. Without `moderator`, the case is recorded as System. + CreateCasebookCase *bool `json:"createCasebookCase,omitzero"` +} + +func (r *RejectContentRequestBody) GetReason() *string { + if r == nil { + return nil + } + return r.Reason +} + +func (r *RejectContentRequestBody) GetQueueID() *string { + if r == nil { + return nil + } + return r.QueueID +} + +func (r *RejectContentRequestBody) GetModerator() *string { + if r == nil { + return nil + } + return r.Moderator +} + +func (r *RejectContentRequestBody) GetCreateCasebookCase() *bool { + if r == nil { + return nil + } + return r.CreateCasebookCase +} + +type RejectContentRequest struct { + // The content ID you sent when you submitted the content, or the ID Moderation API assigned. + ID string `pathParam:"style=simple,explode=false,name=id"` + // The project to act on, by ID or name. Required with an organization key unless the key reaches only one project; list projects with GET /projects. A project key acts on its own project and needs no value: naming another one is an error. + ProjectID *string `header:"style=simple,explode=false,name=Project-Id"` + Body RejectContentRequestBody `request:"mediaType=application/json"` +} + +func (r *RejectContentRequest) GetID() string { + if r == nil { + return "" + } + return r.ID +} + +func (r *RejectContentRequest) GetProjectID() *string { + if r == nil { + return nil + } + return r.ProjectID +} + +func (r *RejectContentRequest) GetBody() RejectContentRequestBody { + if r == nil { + return RejectContentRequestBody{} + } + return r.Body +} + +// RejectContentResponseBody - Execution result +type RejectContentResponseBody struct { + // Whether the action was executed successfully + Success bool `json:"success"` +} + +func (r *RejectContentResponseBody) GetSuccess() bool { + if r == nil { + return false + } + return r.Success +} + +type RejectContentResponse struct { + HTTPMeta components.HTTPMetadata `json:"-"` + // Action executed successfully + Object *RejectContentResponseBody +} + +func (r RejectContentResponse) MarshalJSON() ([]byte, error) { + return utils.MarshalJSON(r, "", false) +} + +func (r *RejectContentResponse) UnmarshalJSON(data []byte) error { + if err := utils.UnmarshalJSON(data, &r, "", false, nil); err != nil { + return err + } + return nil +} + +func (r *RejectContentResponse) GetHTTPMeta() components.HTTPMetadata { + if r == nil { + return components.HTTPMetadata{} + } + return r.HTTPMeta +} + +func (r *RejectContentResponse) GetObject() *RejectContentResponseBody { + if r == nil { + return nil + } + return r.Object +} diff --git a/internal/sdk/models/operations/suspendauthor.go b/internal/sdk/models/operations/suspendauthor.go new file mode 100644 index 0000000..49fe7cf --- /dev/null +++ b/internal/sdk/models/operations/suspendauthor.go @@ -0,0 +1,102 @@ +// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + +package operations + +import ( + "github.com/moderation-api/cli/internal/sdk/models/components" + "github.com/moderation-api/cli/internal/sdk/sdkinternal/utils" +) + +type SuspendAuthorRequestBody struct { + // Why the action was taken. Saved with the action and sent in the webhook event. + Reason *string `json:"reason,omitzero"` + // How long the suspension lasts, in milliseconds. Defaults to 24 hours. + Duration *float64 `json:"duration,omitzero"` +} + +func (s *SuspendAuthorRequestBody) GetReason() *string { + if s == nil { + return nil + } + return s.Reason +} + +func (s *SuspendAuthorRequestBody) GetDuration() *float64 { + if s == nil { + return nil + } + return s.Duration +} + +type SuspendAuthorRequest struct { + // Either external ID or the ID assigned by moderation API. + ID string `pathParam:"style=simple,explode=false,name=id"` + // The project to act on, by ID or name. Required with an organization key unless the key reaches only one project; list projects with GET /projects. A project key acts on its own project and needs no value: naming another one is an error. + ProjectID *string `header:"style=simple,explode=false,name=Project-Id"` + Body SuspendAuthorRequestBody `request:"mediaType=application/json"` +} + +func (s *SuspendAuthorRequest) GetID() string { + if s == nil { + return "" + } + return s.ID +} + +func (s *SuspendAuthorRequest) GetProjectID() *string { + if s == nil { + return nil + } + return s.ProjectID +} + +func (s *SuspendAuthorRequest) GetBody() SuspendAuthorRequestBody { + if s == nil { + return SuspendAuthorRequestBody{} + } + return s.Body +} + +// SuspendAuthorResponseBody - Execution result +type SuspendAuthorResponseBody struct { + // Whether the action was executed successfully + Success bool `json:"success"` +} + +func (s *SuspendAuthorResponseBody) GetSuccess() bool { + if s == nil { + return false + } + return s.Success +} + +type SuspendAuthorResponse struct { + HTTPMeta components.HTTPMetadata `json:"-"` + // Action executed successfully + Object *SuspendAuthorResponseBody +} + +func (s SuspendAuthorResponse) MarshalJSON() ([]byte, error) { + return utils.MarshalJSON(s, "", false) +} + +func (s *SuspendAuthorResponse) UnmarshalJSON(data []byte) error { + if err := utils.UnmarshalJSON(data, &s, "", false, nil); err != nil { + return err + } + return nil +} + +func (s *SuspendAuthorResponse) GetHTTPMeta() components.HTTPMetadata { + if s == nil { + return components.HTTPMetadata{} + } + return s.HTTPMeta +} + +func (s *SuspendAuthorResponse) GetObject() *SuspendAuthorResponseBody { + if s == nil { + return nil + } + return s.Object +} diff --git a/internal/sdk/models/operations/unblockauthor.go b/internal/sdk/models/operations/unblockauthor.go new file mode 100644 index 0000000..cd417ed --- /dev/null +++ b/internal/sdk/models/operations/unblockauthor.go @@ -0,0 +1,93 @@ +// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + +package operations + +import ( + "github.com/moderation-api/cli/internal/sdk/models/components" + "github.com/moderation-api/cli/internal/sdk/sdkinternal/utils" +) + +type UnblockAuthorRequestBody struct { + // Why the action was taken. Saved with the action and sent in the webhook event. + Reason *string `json:"reason,omitzero"` +} + +func (u *UnblockAuthorRequestBody) GetReason() *string { + if u == nil { + return nil + } + return u.Reason +} + +type UnblockAuthorRequest struct { + // Either external ID or the ID assigned by moderation API. + ID string `pathParam:"style=simple,explode=false,name=id"` + // The project to act on, by ID or name. Required with an organization key unless the key reaches only one project; list projects with GET /projects. A project key acts on its own project and needs no value: naming another one is an error. + ProjectID *string `header:"style=simple,explode=false,name=Project-Id"` + Body UnblockAuthorRequestBody `request:"mediaType=application/json"` +} + +func (u *UnblockAuthorRequest) GetID() string { + if u == nil { + return "" + } + return u.ID +} + +func (u *UnblockAuthorRequest) GetProjectID() *string { + if u == nil { + return nil + } + return u.ProjectID +} + +func (u *UnblockAuthorRequest) GetBody() UnblockAuthorRequestBody { + if u == nil { + return UnblockAuthorRequestBody{} + } + return u.Body +} + +// UnblockAuthorResponseBody - Execution result +type UnblockAuthorResponseBody struct { + // Whether the action was executed successfully + Success bool `json:"success"` +} + +func (u *UnblockAuthorResponseBody) GetSuccess() bool { + if u == nil { + return false + } + return u.Success +} + +type UnblockAuthorResponse struct { + HTTPMeta components.HTTPMetadata `json:"-"` + // Action executed successfully + Object *UnblockAuthorResponseBody +} + +func (u UnblockAuthorResponse) MarshalJSON() ([]byte, error) { + return utils.MarshalJSON(u, "", false) +} + +func (u *UnblockAuthorResponse) UnmarshalJSON(data []byte) error { + if err := utils.UnmarshalJSON(data, &u, "", false, nil); err != nil { + return err + } + return nil +} + +func (u *UnblockAuthorResponse) GetHTTPMeta() components.HTTPMetadata { + if u == nil { + return components.HTTPMetadata{} + } + return u.HTTPMeta +} + +func (u *UnblockAuthorResponse) GetObject() *UnblockAuthorResponseBody { + if u == nil { + return nil + } + return u.Object +} diff --git a/internal/sdk/moderationapi.go b/internal/sdk/moderationapi.go index d2a0f19..8afdde1 100644 --- a/internal/sdk/moderationapi.go +++ b/internal/sdk/moderationapi.go @@ -57,11 +57,11 @@ type ModerationAPI struct { Feedback *Feedback Queue *Queue Actions *Actions + Content *Content Webhooks *Webhooks WebhookSecret *WebhookSecret Account *Account Wordlist *Wordlist - Content *Content Projects *Projects Channels *Channels @@ -139,10 +139,10 @@ func WithTimeout(timeout time.Duration) SDKOption { // New creates a new instance of the SDK with the provided options func New(opts ...SDKOption) *ModerationAPI { sdk := &ModerationAPI{ - SDKVersion: "0.5.0", + SDKVersion: "0.6.0", sdkConfiguration: config.SDKConfiguration{ - UserAgent: "speakeasy-sdk/go 0.5.0 internal 1.1.0 github.com/moderation-api/cli/internal/sdk", - SDKVersion: "0.5.0", + UserAgent: "speakeasy-sdk/go 0.6.0 internal 1.1.0 github.com/moderation-api/cli/internal/sdk", + SDKVersion: "0.6.0", GenVersion: "internal", OpenAPIDocVersion: "1.1.0", ServerList: ServerList, @@ -165,11 +165,11 @@ func New(opts ...SDKOption) *ModerationAPI { sdk.Feedback = newFeedback(sdk, sdk.sdkConfiguration, sdk.hooks) sdk.Queue = newQueue(sdk, sdk.sdkConfiguration, sdk.hooks) sdk.Actions = newActions(sdk, sdk.sdkConfiguration, sdk.hooks) + sdk.Content = newContent(sdk, sdk.sdkConfiguration, sdk.hooks) sdk.Webhooks = newWebhooks(sdk, sdk.sdkConfiguration, sdk.hooks) sdk.WebhookSecret = newWebhookSecret(sdk, sdk.sdkConfiguration, sdk.hooks) sdk.Account = newAccount(sdk, sdk.sdkConfiguration, sdk.hooks) sdk.Wordlist = newWordlist(sdk, sdk.sdkConfiguration, sdk.hooks) - sdk.Content = newContent(sdk, sdk.sdkConfiguration, sdk.hooks) sdk.Projects = newProjects(sdk, sdk.sdkConfiguration, sdk.hooks) sdk.Channels = newChannels(sdk, sdk.sdkConfiguration, sdk.hooks) diff --git a/internal/usage/bodyschemas.go b/internal/usage/bodyschemas.go index dd72237..34c7e07 100644 --- a/internal/usage/bodyschemas.go +++ b/internal/usage/bodyschemas.go @@ -9,6 +9,8 @@ import ( var bodySchemas = map[string]string{ "addWordlistWords": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"words\":{\"description\":\"Array of words to add to the wordlist. Duplicate words will be ignored.\",\"items\":{\"type\":\"string\"},\"type\":\"array\"\x7d\x7d,\"required\":[\"words\"],\"type\":\"object\"}", + "allowContent": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"createCasebookCase\":{\"description\":\"Record an allow or reject decision as a casebook case. Defaults to true when `moderator` is set, else false. Without `moderator`, the case is recorded as System.\",\"type\":\"boolean\"},\"moderator\":{\"description\":\"The user ID or email of the organization member who made the decision. The action is recorded as theirs.\",\"type\":\"string\"},\"queueId\":{\"description\":\"The queue the content is reviewed in. Defaults to the project's default queue.\",\"type\":\"string\"},\"reason\":{\"description\":\"Why the action was taken. Saved with the action and sent in the webhook event.\",\"type\":\"string\"\x7d\x7d,\"type\":\"object\"}", + "blockAuthor": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"reason\":{\"description\":\"Why the action was taken. Saved with the action and sent in the webhook event.\",\"type\":\"string\"\x7d\x7d,\"type\":\"object\"}", "createAction": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"builtIn\":{\"default\":false,\"description\":\"Whether the action is a built-in action or a custom one.\",\"type\":[\"boolean\",\"null\"]},\"description\":{\"description\":\"The description of the action.\",\"type\":[\"string\",\"null\"]},\"filterInQueueIds\":{\"default\":[],\"description\":\"The IDs of the queues the action is available in.\",\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"freeText\":{\"default\":false,\"description\":\"Whether the action allows any text to be entered as a value or if it must be one of the possible values.\",\"type\":\"boolean\"},\"key\":{\"description\":\"User defined key of the action.\",\"type\":[\"string\",\"null\"]},\"name\":{\"description\":\"The name of the action.\",\"type\":\"string\"},\"position\":{\"default\":\"ALL_QUEUES\",\"description\":\"Show the action in all queues, selected queues or no queues (to use via API only).\",\"enum\":[\"ALL_QUEUES\",\"SOME_QUEUES\",\"HIDDEN\"],\"type\":\"string\"},\"possibleValues\":{\"default\":[],\"description\":\"The possible values of the action. The user will be prompted to select one of these values when executing the action.\",\"items\":{\"properties\":{\"value\":{\"description\":\"The value of the action.\",\"type\":\"string\"\x7d\x7d,\"required\":[\"value\"],\"type\":\"object\"},\"type\":\"array\"},\"queueBehaviour\":{\"default\":\"NO_CHANGE\",\"description\":\"Whether the action resolves and removes the item, unresolves and re-add it to the queue, or does not change the resolve status.\",\"enum\":[\"REMOVE\",\"ADD\",\"NO_CHANGE\"],\"type\":\"string\"},\"type\":{\"anyOf\":[{\"enum\":[\"AUTHOR_BLOCK\",\"AUTHOR_BLOCK_TEMP\",\"AUTHOR_UNBLOCK\",\"AUTHOR_DELETE\",\"AUTHOR_REPORT\",\"AUTHOR_WARN\",\"AUTHOR_CUSTOM\",\"ITEM_REJECT\",\"ITEM_ALLOW\",\"ITEM_CUSTOM\"],\"type\":\"string\"},{\"type\":\"null\"}],\"description\":\"The type of the action.\"},\"valueRequired\":{\"default\":false,\"description\":\"Whether the action requires a value to be executed.\",\"type\":\"boolean\"\x7d\x7d,\"required\":[\"name\"],\"type\":\"object\"}", "createAuthor": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"company\":{\"description\":\"The author's company or organization\",\"type\":[\"string\",\"null\"]},\"email\":{\"anyOf\":[{\"format\":\"email\",\"pattern\":\"^(?:[A-Za-z0-9_'+\\\\-]+\\\\.)*[A-Za-z0-9_'+\\\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\\\-]*\\\\.)+[A-Za-z]{2,}$\",\"type\":\"string\"},{\"type\":\"null\"}],\"description\":\"Author email address\"},\"external_id\":{\"description\":\"External ID of the user, typically the ID of the author in your database.\",\"type\":\"string\"},\"external_link\":{\"anyOf\":[{\"format\":\"uri\",\"type\":\"string\"},{\"type\":\"null\"}],\"description\":\"URL of the author's external profile\"},\"first_seen\":{\"description\":\"Timestamp when author first appeared\",\"type\":\"number\"},\"last_seen\":{\"description\":\"Timestamp of last activity\",\"type\":\"number\"},\"manual_trust_level\":{\"anyOf\":[{\"maximum\":4,\"minimum\":-1,\"type\":\"number\"},{\"type\":\"null\"}]},\"metadata\":{\"additionalProperties\":{},\"description\":\"Additional metadata provided by your system. We recommend including any relevant information that may assist in the moderation process.\",\"properties\":{\"email_verified\":{\"description\":\"Whether the author's email is verified\",\"type\":[\"boolean\",\"null\"]},\"identity_verified\":{\"description\":\"Whether the author's identity is verified\",\"type\":[\"boolean\",\"null\"]},\"is_paying_customer\":{\"description\":\"Whether the author is a paying customer\",\"type\":[\"boolean\",\"null\"]},\"phone_verified\":{\"description\":\"Whether the author's phone number is verified\",\"type\":[\"boolean\",\"null\"]\x7d\x7d,\"type\":\"object\"},\"name\":{\"description\":\"Author name or identifier\",\"type\":[\"string\",\"null\"]},\"profile_picture\":{\"anyOf\":[{\"format\":\"uri\",\"type\":\"string\"},{\"type\":\"null\"}],\"description\":\"URL of the author's profile picture\"\x7d\x7d,\"required\":[\"external_id\"],\"type\":\"object\"}", "createChannel": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"contentType\":{\"description\":\"The kind of content the channel moderates.\",\"enum\":[\"profile\",\"message\",\"post\",\"comment\",\"event\",\"product\",\"review\",\"voice\",\"other\"],\"type\":\"string\"},\"description\":{\"description\":\"The channel's description.\",\"maxLength\":500,\"type\":\"string\"},\"key\":{\"description\":\"The key you pass as `channel` when you moderate content. Defaults to the name in lowercase with dashes. Must be unique in the project.\",\"maxLength\":100,\"minLength\":1,\"pattern\":\"^[A-Za-z0-9_-]+$\",\"type\":\"string\"},\"name\":{\"description\":\"The channel's name.\",\"maxLength\":100,\"minLength\":1,\"type\":\"string\"\x7d\x7d,\"required\":[\"name\"],\"type\":\"object\"}", @@ -19,9 +21,12 @@ var bodySchemas = map[string]string{ "createWordlist": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"description\":{\"description\":\"Description of the wordlist\",\"type\":\"string\"},\"key\":{\"description\":\"Key of the wordlist. Default: made from the name. If the key is already in use, a suffix is added.\",\"type\":\"string\"},\"name\":{\"description\":\"Name of the wordlist\",\"maxLength\":100,\"minLength\":1,\"type\":\"string\"},\"words\":{\"description\":\"Words to add to the wordlist. Words are converted to lowercase. Duplicate and empty words are ignored. Maximum 5000 words.\",\"items\":{\"type\":\"string\"},\"type\":\"array\"\x7d\x7d,\"required\":[\"name\"],\"type\":\"object\"}", "duplicateChannel": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"key\":{\"description\":\"Key of the copy. Defaults to the original key with a timestamp. Must be unique in the project.\",\"maxLength\":100,\"minLength\":1,\"pattern\":\"^[A-Za-z0-9_-]+$\",\"type\":\"string\"},\"name\":{\"description\":\"Name of the copy. Defaults to the original name with \\\"(copy)\\\".\",\"maxLength\":100,\"minLength\":1,\"type\":\"string\"\x7d\x7d,\"type\":\"object\"}", "duplicateProject": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"name\":{\"description\":\"Name of the copy. Defaults to the original name with \\\"(copy)\\\".\",\"maxLength\":75,\"minLength\":1,\"type\":\"string\"\x7d\x7d,\"type\":\"object\"}", - "executeAction": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"description\":\"Input parameters\",\"properties\":{\"actionKey\":{\"description\":\"ID or key of the action to execute\",\"type\":\"string\"},\"authorIds\":{\"description\":\"IDs of the authors to apply the action to. Provide this or contentIds.\",\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"contentIds\":{\"description\":\"IDs of the content items to apply the action to. Provide this or authorIds.\",\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"duration\":{\"description\":\"Optional duration in milliseconds for actions with timeouts\",\"minimum\":0,\"type\":\"number\"},\"queueId\":{\"description\":\"Optional queue ID if the action is queue-specific\",\"type\":\"string\"},\"value\":{\"description\":\"Optional value to provide with the action\",\"type\":\"string\"\x7d\x7d,\"required\":[\"actionKey\"],\"type\":\"object\"}", + "executeAction": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"description\":\"Input parameters\",\"properties\":{\"actionKey\":{\"description\":\"ID or key of the action to execute\",\"type\":\"string\"},\"authorIds\":{\"description\":\"IDs of the authors to apply the action to. Provide this or contentIds.\",\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"contentIds\":{\"description\":\"IDs of the content items to apply the action to. Provide this or authorIds.\",\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"createCasebookCase\":{\"description\":\"Record an allow or reject decision as a casebook case. Defaults to true when `moderator` is set, else false. Without `moderator`, the case is recorded as System.\",\"type\":\"boolean\"},\"duration\":{\"description\":\"Optional duration in milliseconds for actions with timeouts\",\"minimum\":0,\"type\":\"number\"},\"moderator\":{\"description\":\"The user ID or email of the organization member who made the decision. The action is recorded as theirs.\",\"type\":\"string\"},\"queueId\":{\"description\":\"Optional queue ID if the action is queue-specific\",\"type\":\"string\"},\"value\":{\"description\":\"Optional value to provide with the action\",\"type\":\"string\"\x7d\x7d,\"required\":[\"actionKey\"],\"type\":\"object\"}", + "rejectContent": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"createCasebookCase\":{\"description\":\"Record an allow or reject decision as a casebook case. Defaults to true when `moderator` is set, else false. Without `moderator`, the case is recorded as System.\",\"type\":\"boolean\"},\"moderator\":{\"description\":\"The user ID or email of the organization member who made the decision. The action is recorded as theirs.\",\"type\":\"string\"},\"queueId\":{\"description\":\"The queue the content is reviewed in. Defaults to the project's default queue.\",\"type\":\"string\"},\"reason\":{\"description\":\"Why the action was taken. Saved with the action and sent in the webhook event.\",\"type\":\"string\"\x7d\x7d,\"type\":\"object\"}", "resolveQueueItems": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"itemIds\":{\"description\":\"The item IDs to resolve. Up to 100.\",\"items\":{\"type\":\"string\"},\"maxItems\":100,\"minItems\":1,\"type\":\"array\"\x7d\x7d,\"required\":[\"itemIds\"],\"type\":\"object\"}", "submitContent": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"authorId\":{\"description\":\"The author of the content.\",\"type\":\"string\"},\"channel\":{\"description\":\"Provide a channel ID or key. Will use the project's default channel if not provided.\",\"type\":\"string\"},\"clientAction\":{\"description\":\"A recommendation from your own client-side flagging (e.g. a banned-IP list or a third-party tool). Feeds the rules engine and can escalate or override the recommended action. Does not change whether our analysis flagged the content.\",\"properties\":{\"action\":{\"description\":\"Your recommendation for the content: allow, review, or reject.\",\"enum\":[\"review\",\"allow\",\"reject\"],\"type\":\"string\"},\"behavior\":{\"description\":\"How your recommendation combines with ours. Defaults to 'escalate', which only applies it when stricter than ours; 'override' replaces ours outright.\",\"enum\":[\"override\",\"escalate\"],\"type\":\"string\"},\"reason\":{\"description\":\"A human-readable explanation for your recommendation.\",\"maxLength\":500,\"type\":\"string\"},\"source\":{\"description\":\"Where your recommendation came from, e.g. \\\"banned-ip\\\".\",\"maxLength\":100,\"type\":\"string\"\x7d\x7d,\"required\":[\"action\"],\"type\":\"object\"},\"content\":{\"description\":\"The content sent for moderation\",\"oneOf\":[{\"description\":\"Text\",\"properties\":{\"text\":{\"description\":\"The content text\",\"maxLength\":100000,\"type\":\"string\"},\"type\":{\"const\":\"text\",\"type\":\"string\"\x7d\x7d,\"required\":[\"type\",\"text\"],\"title\":\"Text\",\"type\":\"object\"},{\"description\":\"Image\",\"properties\":{\"data\":{\"description\":\"Base64-encoded image data. Either url or data must be provided. Note: base64 images are not stored and will not appear in the review queue.\",\"maxLength\":5500000,\"type\":\"string\"},\"type\":{\"const\":\"image\",\"type\":\"string\"},\"url\":{\"description\":\"A public URL of the image content. Either url or data must be provided.\",\"format\":\"uri\",\"type\":\"string\"\x7d\x7d,\"required\":[\"type\"],\"title\":\"Image\",\"type\":\"object\"},{\"description\":\"Video\",\"properties\":{\"type\":{\"const\":\"video\",\"type\":\"string\"},\"url\":{\"description\":\"A public URL of the video content\",\"format\":\"uri\",\"type\":\"string\"\x7d\x7d,\"required\":[\"type\",\"url\"],\"title\":\"Video\",\"type\":\"object\"},{\"description\":\"Audio\",\"properties\":{\"type\":{\"const\":\"audio\",\"type\":\"string\"},\"url\":{\"description\":\"The URL of the audio content\",\"format\":\"uri\",\"type\":\"string\"\x7d\x7d,\"required\":[\"type\",\"url\"],\"title\":\"Audio\",\"type\":\"object\"},{\"description\":\"Object\",\"properties\":{\"data\":{\"additionalProperties\":{\"oneOf\":[{\"description\":\"Text\",\"properties\":{\"text\":{\"description\":\"The content text\",\"maxLength\":100000,\"type\":\"string\"},\"type\":{\"const\":\"text\",\"type\":\"string\"\x7d\x7d,\"required\":[\"type\",\"text\"],\"title\":\"Text\",\"type\":\"object\"},{\"description\":\"Image\",\"properties\":{\"data\":{\"description\":\"Base64-encoded image data. Either url or data must be provided. Note: base64 images are not stored and will not appear in the review queue.\",\"maxLength\":5500000,\"type\":\"string\"},\"type\":{\"const\":\"image\",\"type\":\"string\"},\"url\":{\"description\":\"A public URL of the image content. Either url or data must be provided.\",\"format\":\"uri\",\"type\":\"string\"\x7d\x7d,\"required\":[\"type\"],\"title\":\"Image\",\"type\":\"object\"},{\"description\":\"Video\",\"properties\":{\"type\":{\"const\":\"video\",\"type\":\"string\"},\"url\":{\"description\":\"A public URL of the video content\",\"format\":\"uri\",\"type\":\"string\"\x7d\x7d,\"required\":[\"type\",\"url\"],\"title\":\"Video\",\"type\":\"object\"},{\"description\":\"Audio\",\"properties\":{\"type\":{\"const\":\"audio\",\"type\":\"string\"},\"url\":{\"description\":\"The URL of the audio content\",\"format\":\"uri\",\"type\":\"string\"\x7d\x7d,\"required\":[\"type\",\"url\"],\"title\":\"Audio\",\"type\":\"object\"}],\"title\":\"Content leaf\",\"type\":\"object\"},\"description\":\"Values in the object. Can be mixed content types.\",\"propertyNames\":{\"description\":\"The key of the content node\",\"type\":\"string\"},\"type\":\"object\"},\"type\":{\"const\":\"object\",\"type\":\"string\"\x7d\x7d,\"required\":[\"type\",\"data\"],\"title\":\"Object\",\"type\":\"object\"}],\"type\":\"object\"},\"contentId\":{\"description\":\"The unique ID of the content in your database.\",\"maxLength\":100,\"type\":\"string\"},\"conversationId\":{\"description\":\"For example the ID of a chat room or a post\",\"type\":\"string\"},\"doNotStore\":{\"description\":\"Process the content without saving it on our servers. The item is still created and appears in the review queue, analytics and author history with its moderation results, but its content is empty, so reviewers cannot see what was submitted.\",\"type\":\"boolean\"},\"metaType\":{\"description\":\"The meta type of content being moderated\",\"enum\":[\"profile\",\"message\",\"post\",\"comment\",\"event\",\"product\",\"review\",\"voice\",\"other\"],\"type\":\"string\"},\"metadata\":{\"additionalProperties\":{},\"description\":\"Any metadata you want to store with the content\",\"propertyNames\":{\"type\":\"string\"},\"type\":\"object\"},\"policies\":{\"description\":\"(Enterprise) override the channel policies for this moderation request only.\",\"items\":{\"oneOf\":[{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"toxicity\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Toxicity\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"personal_information\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Personal Information\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"hate\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Hate\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"illicit\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Illicit\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"illicit_drugs\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Illicit Drugs\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"illicit_alcohol\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Illicit Alcohol\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"illicit_firearms\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Illicit Firearms\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"illicit_tobacco\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Illicit Tobacco\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"illicit_gambling\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Illicit Gambling\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"cannabis\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Cannabis\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"adult\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Adult\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"crypto\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Crypto\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"sexual\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Sexual\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"sexual_minors\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Sexual Minors\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"profanity\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Profanity\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"violence\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Violence\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"self_harm\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Self Harm\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"spam\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Spam\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"low_quality\",\"type\":\"string\"},\"minWords\":{\"description\":\"Flag content with fewer than this many words as low-effort. Defaults to 3. Set to disable by omitting.\",\"minimum\":1,\"type\":\"integer\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Low-quality content\",\"type\":\"object\"},{\"properties\":{\"comparator\":{\"description\":\"Flag images that contain \\\"at least\\\" or \\\"fewer than\\\" the configured number of faces. Defaults to at_least.\",\"enum\":[\"at_least\",\"fewer_than\"],\"type\":\"string\"},\"count\":{\"description\":\"Number of faces the comparator applies to. Defaults to 1, so the default rule flags any image containing a face.\",\"minimum\":0,\"type\":\"integer\"},\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"face_detection\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Face detection\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"self_promotion\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Self Promotion\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"political\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Political\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"religion\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Religion\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"threat\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Threat\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"sexual_solicitation\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Sexual Solicitation\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"channel_circumvent\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Channel Circumvention\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"illegal_trade\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Illegal Trade\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"alcohol\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Alcohol\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"drugs\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Drugs\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"gambling\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Gambling\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"tobacco\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Tobacco\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"weapons\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Weapons\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"promotion\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Promotion\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"extremism\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Extremism\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"code_abuse\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Code Abuse\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"id\":{\"const\":\"unicode_spoofing\",\"type\":\"string\"},\"signals\":{\"additionalProperties\":{\"properties\":{\"flag\":{\"type\":\"boolean\"\x7d\x7d,\"type\":\"object\"},\"description\":\"Per-signal flag toggles. Omitted spoofing signals are enabled; encoding_damage defaults to off because decode damage (U+FFFD) marks a broken pipeline, not an attack. A disabled signal is still detected and reported as a label, but does not by itself flag the policy.\",\"propertyNames\":{\"enum\":[\"mixed_script\",\"confusable_word\",\"invisible\",\"zalgo\",\"illegal\",\"encoding_damage\"],\"type\":\"string\"},\"type\":\"object\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"Unicode Spoofing\",\"type\":\"object\"},{\"properties\":{\"entities\":{\"additionalProperties\":{\"additionalProperties\":{},\"properties\":{\"enable\":{\"type\":\"boolean\"},\"flag\":{\"type\":\"boolean\"},\"mask\":{\"type\":\"string\"},\"shouldMask\":{\"type\":\"boolean\"\x7d\x7d,\"type\":\"object\"},\"propertyNames\":{\"enum\":[\"email\",\"phone\",\"url\",\"address\",\"name\",\"username\",\"ip_address\",\"credit_card\",\"sensitive_other\"],\"type\":\"string\"},\"type\":\"object\"},\"id\":{\"const\":\"pii\",\"type\":\"string\"\x7d\x7d,\"required\":[\"id\",\"entities\"],\"title\":\"PII Masking\",\"type\":\"object\"},{\"properties\":{\"entities\":{\"additionalProperties\":{\"additionalProperties\":{},\"properties\":{\"enable\":{\"type\":\"boolean\"},\"flag\":{\"type\":\"boolean\"},\"mask\":{\"type\":\"string\"},\"shouldMask\":{\"type\":\"boolean\"\x7d\x7d,\"type\":\"object\"},\"propertyNames\":{\"enum\":[\"email\",\"phone\",\"url\",\"address\",\"name\",\"username\",\"ip_address\",\"credit_card\",\"sensitive_other\"],\"type\":\"string\"},\"type\":\"object\"},\"id\":{\"const\":\"url\",\"type\":\"string\"\x7d\x7d,\"required\":[\"id\",\"entities\"],\"title\":\"URL Masking\",\"type\":\"object\"},{\"properties\":{\"allowlistWordlistIds\":{\"description\":\"IDs of wordlists whose entries are treated as allowed URL domains. Matches short-circuit the risk model and are never flagged.\",\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"blocklistWordlistIds\":{\"description\":\"IDs of wordlists whose entries are treated as blocked URL domains. Matches short-circuit the risk model and are always flagged. Blocklists take precedence over allowlists.\",\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"flag\":{\"type\":\"boolean\"},\"flagLinkShorteners\":{\"description\":\"When true, any URL detected as a free link shortener (bit.ly, t.co, tinyurl, etc.) is always flagged regardless of risk score. Allowlist matches still win.\",\"type\":\"boolean\"},\"id\":{\"const\":\"url_risk\",\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\"],\"title\":\"URL Risk\",\"type\":\"object\"},{\"properties\":{\"flag\":{\"type\":\"boolean\"},\"guidelineKey\":{\"type\":\"string\"},\"id\":{\"const\":\"guideline\",\"type\":\"string\"},\"instructions\":{\"type\":\"string\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"\x7d\x7d,\"required\":[\"flag\",\"id\",\"instructions\",\"guidelineKey\"],\"title\":\"Guideline\",\"type\":\"object\"}],\"type\":\"object\"},\"type\":\"array\"},\"timestamp\":{\"description\":\"Unix timestamp (in milliseconds) of when the content was created. Use if content is not submitted in real-time.\",\"type\":\"number\"\x7d\x7d,\"required\":[\"content\"],\"type\":\"object\"}", + "suspendAuthor": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"duration\":{\"description\":\"How long the suspension lasts, in milliseconds. Defaults to 24 hours.\",\"minimum\":0,\"type\":\"number\"},\"reason\":{\"description\":\"Why the action was taken. Saved with the action and sent in the webhook event.\",\"type\":\"string\"\x7d\x7d,\"type\":\"object\"}", + "unblockAuthor": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"reason\":{\"description\":\"Why the action was taken. Saved with the action and sent in the webhook event.\",\"type\":\"string\"\x7d\x7d,\"type\":\"object\"}", "updateAction": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"builtIn\":{\"default\":false,\"description\":\"Whether the action is a built-in action or a custom one.\",\"type\":[\"boolean\",\"null\"]},\"description\":{\"description\":\"The description of the action.\",\"type\":[\"string\",\"null\"]},\"filterInQueueIds\":{\"default\":[],\"description\":\"The IDs of the queues the action is available in.\",\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"freeText\":{\"default\":false,\"description\":\"Whether the action allows any text to be entered as a value or if it must be one of the possible values.\",\"type\":\"boolean\"},\"key\":{\"description\":\"User defined key of the action.\",\"type\":[\"string\",\"null\"]},\"name\":{\"description\":\"The name of the action.\",\"type\":\"string\"},\"position\":{\"default\":\"ALL_QUEUES\",\"description\":\"Show the action in all queues, selected queues or no queues (to use via API only).\",\"enum\":[\"ALL_QUEUES\",\"SOME_QUEUES\",\"HIDDEN\"],\"type\":\"string\"},\"possibleValues\":{\"default\":[],\"description\":\"The possible values of the action. The user will be prompted to select one of these values when executing the action.\",\"items\":{\"properties\":{\"value\":{\"description\":\"The value of the action.\",\"type\":\"string\"\x7d\x7d,\"required\":[\"value\"],\"type\":\"object\"},\"type\":\"array\"},\"queueBehaviour\":{\"default\":\"NO_CHANGE\",\"description\":\"Whether the action resolves and removes the item, unresolves and re-add it to the queue, or does not change the resolve status.\",\"enum\":[\"REMOVE\",\"ADD\",\"NO_CHANGE\"],\"type\":\"string\"},\"type\":{\"anyOf\":[{\"enum\":[\"AUTHOR_BLOCK\",\"AUTHOR_BLOCK_TEMP\",\"AUTHOR_UNBLOCK\",\"AUTHOR_DELETE\",\"AUTHOR_REPORT\",\"AUTHOR_WARN\",\"AUTHOR_CUSTOM\",\"ITEM_REJECT\",\"ITEM_ALLOW\",\"ITEM_CUSTOM\"],\"type\":\"string\"},{\"type\":\"null\"}],\"description\":\"The type of the action.\"},\"valueRequired\":{\"default\":false,\"description\":\"Whether the action requires a value to be executed.\",\"type\":\"boolean\"\x7d\x7d,\"type\":\"object\"}", "updateAuthor": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"company\":{\"description\":\"The author's company or organization\",\"type\":[\"string\",\"null\"]},\"email\":{\"anyOf\":[{\"format\":\"email\",\"pattern\":\"^(?:[A-Za-z0-9_'+\\\\-]+\\\\.)*[A-Za-z0-9_'+\\\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\\\-]*\\\\.)+[A-Za-z]{2,}$\",\"type\":\"string\"},{\"type\":\"null\"}],\"description\":\"Author email address\"},\"external_link\":{\"anyOf\":[{\"format\":\"uri\",\"type\":\"string\"},{\"type\":\"null\"}],\"description\":\"URL of the author's external profile\"},\"first_seen\":{\"description\":\"Timestamp when author first appeared\",\"type\":\"number\"},\"last_seen\":{\"description\":\"Timestamp of last activity\",\"type\":\"number\"},\"manual_trust_level\":{\"anyOf\":[{\"maximum\":4,\"minimum\":-1,\"type\":\"number\"},{\"type\":\"null\"}]},\"metadata\":{\"additionalProperties\":{},\"description\":\"Additional metadata provided by your system. We recommend including any relevant information that may assist in the moderation process.\",\"properties\":{\"email_verified\":{\"description\":\"Whether the author's email is verified\",\"type\":[\"boolean\",\"null\"]},\"identity_verified\":{\"description\":\"Whether the author's identity is verified\",\"type\":[\"boolean\",\"null\"]},\"is_paying_customer\":{\"description\":\"Whether the author is a paying customer\",\"type\":[\"boolean\",\"null\"]},\"phone_verified\":{\"description\":\"Whether the author's phone number is verified\",\"type\":[\"boolean\",\"null\"]\x7d\x7d,\"type\":\"object\"},\"name\":{\"description\":\"Author name or identifier\",\"type\":[\"string\",\"null\"]},\"profile_picture\":{\"anyOf\":[{\"format\":\"uri\",\"type\":\"string\"},{\"type\":\"null\"}],\"description\":\"URL of the author's profile picture\"\x7d\x7d,\"type\":\"object\"}", "updateChannel": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"properties\":{\"contentType\":{\"description\":\"The kind of content the channel moderates.\",\"enum\":[\"profile\",\"message\",\"post\",\"comment\",\"event\",\"product\",\"review\",\"voice\",\"other\"],\"type\":\"string\"},\"contextEnabled\":{\"description\":\"Whether earlier messages in the conversation are read when moderating a message.\",\"type\":\"boolean\"},\"description\":{\"anyOf\":[{\"maxLength\":500,\"type\":\"string\"},{\"type\":\"null\"}],\"description\":\"The channel's description.\"},\"enableAudioModeration\":{\"description\":\"Whether audio is moderated.\",\"type\":\"boolean\"},\"enableImageModeration\":{\"description\":\"Whether images are moderated.\",\"type\":\"boolean\"},\"enableVideoModeration\":{\"description\":\"Whether videos are moderated.\",\"type\":\"boolean\"},\"enableVoiceModeration\":{\"description\":\"Whether real-time voice streams are moderated.\",\"type\":\"boolean\"},\"flaggingMode\":{\"description\":\"`ACTIVE` flags content. `DRY_RUN` evaluates content without flagging it.\",\"enum\":[\"DRY_RUN\",\"ACTIVE\"],\"type\":\"string\"},\"frameCaptureInterval\":{\"description\":\"Seconds between frames captured from a video.\",\"maximum\":60,\"minimum\":0.1,\"type\":\"number\"},\"includeTranscriptInResponse\":{\"description\":\"Whether audio and video transcripts are returned in responses.\",\"type\":\"boolean\"},\"key\":{\"description\":\"The key you pass as `channel` when you moderate content. Changing it breaks integrations that send the old key.\",\"maxLength\":100,\"minLength\":1,\"pattern\":\"^[A-Za-z0-9_-]+$\",\"type\":\"string\"},\"maxFramesPerVideo\":{\"description\":\"The most frames moderated per video.\",\"maximum\":1000,\"minimum\":1,\"type\":\"integer\"},\"name\":{\"description\":\"The channel's name.\",\"maxLength\":100,\"minLength\":1,\"type\":\"string\"},\"spreadFrames\":{\"description\":\"Whether frames are spread evenly across the whole video.\",\"type\":\"boolean\"},\"transcriptionQuality\":{\"description\":\"Trade-off between transcription speed and accuracy.\",\"enum\":[\"SPEED\",\"BALANCED\",\"ACCURACY\"],\"type\":\"string\"},\"unicodeSpoofDetectionEnabled\":{\"description\":\"Whether look-alike Unicode characters are normalized before moderation.\",\"type\":\"boolean\"\x7d\x7d,\"type\":\"object\"}", diff --git a/internal/usage/schema.go b/internal/usage/schema.go index 87cd560..f804666 100644 --- a/internal/usage/schema.go +++ b/internal/usage/schema.go @@ -28,13 +28,13 @@ type usageNode struct { } var usageNodes = map[string]usageNode{ - "": {head: "name \"modapi\"\nbin \"modapi\"\nabout \"Moderation API: Moderate text, images, video and audio in real time, manage authors, review queues and actions, and receive webhooks.\"\nversion \"0.5.0\"\nconfig {\n file \"~/.config/modapi/config.yaml\"\n}", flags: []usageItem{{key: "usage", text: "flag \"--usage\" help=\"Print the CLI Usage schema in KDL format\" global=#true"}, {key: "version", text: "flag \"-v --version\" help=\"Print the CLI version\""}, {key: "output-format", text: "flag \"-o --output-format \" help=\"Specify the output format. Options: pretty, json, yaml, table, toon.\" global=#true config=\"output_format\" default=\"pretty\""}, {key: "color", text: "flag \"--color \" help=\"Control colored output: auto (color when output is a TTY), always, or never. Respects NO_COLOR and FORCE_COLOR env vars.\" global=#true default=\"auto\""}, {key: "jq", text: "flag \"-q --jq \" help=\"Filter and transform output using a jq expression (e.g., '.name', '.items[] | .id')\" global=#true"}, {key: "raw-output", text: "flag \"--raw-output\" help=\"Write --jq string results as raw text instead of JSON strings (like jq -r); non-string results stay JSON\" global=#true default=#false"}, {key: "server-url", text: "flag \"--server-url \" help=\"Override the default server URL\" global=#true"}, {key: "server", text: "flag \"--server \" help=\"Select a server by index (for indexed servers) or name (for named servers)\" global=#true"}, {key: "header", text: "flag \"-H --header \" help=\"Set a custom HTTP request header (format: \\\"Key: Value\\\"). Can be specified multiple times.\" global=#true var=#true"}, {key: "include-headers", text: "flag \"--include-headers\" help=\"Include HTTP response headers in the output\" global=#true default=#false"}, {key: "timeout", text: "flag \"--timeout \" help=\"HTTP request timeout (e.g., 30s, 5m, 100ms)\" global=#true config=\"timeout\""}, {key: "interactive", text: "flag \"--interactive\" help=\"Prompt for missing inputs and open guided configure/auth forms (forms fall back to line prompts on stdin off-TTY)\" global=#true default=#true"}, {key: "no-interactive", text: "flag \"--no-interactive\" help=\"Disable all interactive features (auto-prompting, explorer auto-launch, TUI forms)\" global=#true default=#false"}, {key: "dry-run", text: "flag \"--dry-run\" help=\"Preview API requests without sending them (no network, no OS keychain). Human preview on stderr; with -o json or --jq, one JSON object per request on stdout. Local mutation commands (auth login, auth logout and configure) make no request: they skip prompts and writes and report a no-op (stderr, or one JSON object on stdout in the machine form)\" global=#true default=#false"}, {key: "debug", text: "flag \"-d --debug\" help=\"Log request and response diagnostics to stderr\" global=#true default=#false"}, {key: "agent-mode", text: "flag \"--agent-mode\" help=\"Enable structured errors and default TOON output for AI coding agents. Automatically enabled when a known agent environment is detected (CLAUDECODE, CURSOR_AGENT, etc.). Use --agent-mode=false to disable.\" global=#true default=#false"}, {key: "secret-key", text: "flag \"--secret-key \" help=\"HTTP Bearer\" global=#true env=\"MODAPI_SECRET_KEY\" config=\"security.secret_key\""}, {key: "no-keyring", text: "flag \"--no-keyring\" help=\"Never read or write the OS keychain; store secrets in the config file instead\" global=#true env=\"MODAPI_NO_KEYRING\" config=\"no_keyring\" default=#false"}}, children: []string{"account", "actions", "analytics", "auth", "authors", "channels", "completion", "configure", "content", "explore", "feedback", "help", "projects", "queue", "version", "webhook-secret", "webhooks", "whoami", "wordlist"}}, + "": {head: "name \"modapi\"\nbin \"modapi\"\nabout \"Moderation API: Moderate text, images, video and audio in real time, manage authors, review queues and actions, and receive webhooks.\"\nversion \"0.6.0\"\nconfig {\n file \"~/.config/modapi/config.yaml\"\n}", flags: []usageItem{{key: "usage", text: "flag \"--usage\" help=\"Print the CLI Usage schema in KDL format\" global=#true"}, {key: "version", text: "flag \"-v --version\" help=\"Print the CLI version\""}, {key: "output-format", text: "flag \"-o --output-format \" help=\"Specify the output format. Options: pretty, json, yaml, table, toon.\" global=#true config=\"output_format\" default=\"pretty\""}, {key: "color", text: "flag \"--color \" help=\"Control colored output: auto (color when output is a TTY), always, or never. Respects NO_COLOR and FORCE_COLOR env vars.\" global=#true default=\"auto\""}, {key: "jq", text: "flag \"-q --jq \" help=\"Filter and transform output using a jq expression (e.g., '.name', '.items[] | .id')\" global=#true"}, {key: "raw-output", text: "flag \"--raw-output\" help=\"Write --jq string results as raw text instead of JSON strings (like jq -r); non-string results stay JSON\" global=#true default=#false"}, {key: "server-url", text: "flag \"--server-url \" help=\"Override the default server URL\" global=#true"}, {key: "server", text: "flag \"--server \" help=\"Select a server by index (for indexed servers) or name (for named servers)\" global=#true"}, {key: "header", text: "flag \"-H --header \" help=\"Set a custom HTTP request header (format: \\\"Key: Value\\\"). Can be specified multiple times.\" global=#true var=#true"}, {key: "include-headers", text: "flag \"--include-headers\" help=\"Include HTTP response headers in the output\" global=#true default=#false"}, {key: "timeout", text: "flag \"--timeout \" help=\"HTTP request timeout (e.g., 30s, 5m, 100ms)\" global=#true config=\"timeout\""}, {key: "interactive", text: "flag \"--interactive\" help=\"Prompt for missing inputs and open guided configure/auth forms (forms fall back to line prompts on stdin off-TTY)\" global=#true default=#true"}, {key: "no-interactive", text: "flag \"--no-interactive\" help=\"Disable all interactive features (auto-prompting, explorer auto-launch, TUI forms)\" global=#true default=#false"}, {key: "dry-run", text: "flag \"--dry-run\" help=\"Preview API requests without sending them (no network, no OS keychain). Human preview on stderr; with -o json or --jq, one JSON object per request on stdout. Local mutation commands (auth login, auth logout and configure) make no request: they skip prompts and writes and report a no-op (stderr, or one JSON object on stdout in the machine form)\" global=#true default=#false"}, {key: "debug", text: "flag \"-d --debug\" help=\"Log request and response diagnostics to stderr\" global=#true default=#false"}, {key: "agent-mode", text: "flag \"--agent-mode\" help=\"Enable structured errors and default TOON output for AI coding agents. Automatically enabled when a known agent environment is detected (CLAUDECODE, CURSOR_AGENT, etc.). Use --agent-mode=false to disable.\" global=#true default=#false"}, {key: "secret-key", text: "flag \"--secret-key \" help=\"HTTP Bearer\" global=#true env=\"MODAPI_SECRET_KEY\" config=\"security.secret_key\""}, {key: "no-keyring", text: "flag \"--no-keyring\" help=\"Never read or write the OS keychain; store secrets in the config file instead\" global=#true env=\"MODAPI_NO_KEYRING\" config=\"no_keyring\" default=#false"}}, children: []string{"account", "actions", "analytics", "auth", "authors", "channels", "completion", "configure", "content", "explore", "feedback", "help", "projects", "queue", "version", "webhook-secret", "webhooks", "whoami", "wordlist"}}, "account": {head: "cmd \"account\" help=\"Operations for account\"", children: []string{"retrieve"}}, "account retrieve": {head: "cmd \"retrieve\" help=\"Get account details\"", flags: []usageItem{{key: "project-id", text: " flag \"-p --project-id \" help=\"The project to act on, by ID or name. Required with an organization key unless the key reaches only one project; list projects with GET /projects. A project key acts on its own project and needs no value: naming another one is an error.\""}}}, "actions": {head: "cmd \"actions\" help=\"Operations for actions\"", children: []string{"create", "delete", "execute", "list", "retrieve", "update"}}, "actions create": {head: "cmd \"create\" help=\"Create an action\"", flags: []usageItem{{key: "project-id", text: " flag \"--project-id \" help=\"The project to act on, by ID or name. Required with an organization key unless the key reaches only one project; list projects with GET /projects. A project key acts on its own project and needs no value: naming another one is an error.\""}, {key: "key", text: " flag \"-k --key \" help=\"User defined key of the action.\""}, {key: "name", text: " flag \"-n --name \" help=\"The name of the action. [required]\""}, {key: "description", text: " flag \"--description \" help=\"The description of the action.\""}, {key: "type", text: " flag \"-t --type \" help=\"The type of the action. (options: AUTHOR_BLOCK, AUTHOR_BLOCK_TEMP, AUTHOR_UNBLOCK, AUTHOR_DELETE, AUTHOR_REPORT, AUTHOR_WARN, AUTHOR_CUSTOM, ITEM_REJECT, ITEM_ALLOW, ITEM_CUSTOM)\""}, {key: "built-in", text: " flag \"-b --built-in \" help=\"Whether the action is a built-in action or a custom one.\" default=#false"}, {key: "queue-behaviour", text: " flag \"--queue-behaviour \" help=\"Whether the action resolves and removes the item, unresolves and re-add it to the queue, or does not change the resolve status. (options: REMOVE, ADD, NO_CHANGE)\" default=\"NO_CHANGE\""}, {key: "filter-in-queue-ids", text: " flag \"--filter-in-queue-ids \" help=\"The IDs of the queues the action is available in.\" var=#true"}, {key: "position", text: " flag \"--position \" help=\"Show the action in all queues, selected queues or no queues (to use via API only). (options: ALL_QUEUES, SOME_QUEUES, HIDDEN)\" default=\"ALL_QUEUES\""}, {key: "possible-values", text: " flag \"--possible-values \" help=\"The possible values of the action. The user will be prompted to select one of these values when executing the action. (JSON array)\""}, {key: "value-required", text: " flag \"-v --value-required\" help=\"Whether the action requires a value to be executed.\" default=#false"}, {key: "free-text", text: " flag \"--free-text\" help=\"Whether the action allows any text to be entered as a value or if it must be one of the possible values.\" default=#false"}, {key: "body", text: " flag \"--body \" help=\"Request body as JSON (alternative to individual flags). Can also be provided via stdin; @path reads a file, @- reads stdin to EOF. Use --schema to print the exact JSON Schema.\""}, {key: "schema", text: " flag \"--schema\" help=\"Print the exact JSON Schema of the request body and exit\""}}}, "actions delete": {head: "cmd \"delete\" help=\"Delete an action\"", args: []usageItem{{key: "id", text: " arg \"id\" help=\"The ID of the action to delete. (or use --id)\""}}, flags: []usageItem{{key: "id", text: " flag \"-i --id \" help=\"The ID of the action to delete. (or pass it as the [id] argument)\""}, {key: "project-id", text: " flag \"-p --project-id \" help=\"The project to act on, by ID or name. Required with an organization key unless the key reaches only one project; list projects with GET /projects. A project key acts on its own project and needs no value: naming another one is an error.\""}}}, - "actions execute": {head: "cmd \"execute\" help=\"Execute moderation action\"", flags: []usageItem{{key: "project-id", text: " flag \"-p --project-id \" help=\"The project to act on, by ID or name. Required with an organization key unless the key reaches only one project; list projects with GET /projects. A project key acts on its own project and needs no value: naming another one is an error.\""}, {key: "action-key", text: " flag \"--action-key \" help=\"ID or key of the action to execute [required]\""}, {key: "content-ids", text: " flag \"-c --content-ids \" help=\"IDs of the content items to apply the action to. Provide this or authorIds.\" var=#true"}, {key: "author-ids", text: " flag \"--author-ids \" help=\"IDs of the authors to apply the action to. Provide this or contentIds.\" var=#true"}, {key: "value", text: " flag \"-v --value \" help=\"Optional value to provide with the action\""}, {key: "queue-id", text: " flag \"--queue-id \" help=\"Optional queue ID if the action is queue-specific\""}, {key: "duration", text: " flag \"--duration \" help=\"Optional duration in milliseconds for actions with timeouts\""}, {key: "body", text: " flag \"--body \" help=\"Request body as JSON (alternative to individual flags). Can also be provided via stdin; @path reads a file, @- reads stdin to EOF. Use --schema to print the exact JSON Schema.\""}, {key: "schema", text: " flag \"--schema\" help=\"Print the exact JSON Schema of the request body and exit\""}}}, + "actions execute": {head: "cmd \"execute\" help=\"Execute moderation action\"", flags: []usageItem{{key: "project-id", text: " flag \"-p --project-id \" help=\"The project to act on, by ID or name. Required with an organization key unless the key reaches only one project; list projects with GET /projects. A project key acts on its own project and needs no value: naming another one is an error.\""}, {key: "action-key", text: " flag \"--action-key \" help=\"ID or key of the action to execute [required]\""}, {key: "content-ids", text: " flag \"--content-ids \" help=\"IDs of the content items to apply the action to. Provide this or authorIds.\" var=#true"}, {key: "author-ids", text: " flag \"--author-ids \" help=\"IDs of the authors to apply the action to. Provide this or contentIds.\" var=#true"}, {key: "value", text: " flag \"-v --value \" help=\"Optional value to provide with the action\""}, {key: "queue-id", text: " flag \"--queue-id \" help=\"Optional queue ID if the action is queue-specific\""}, {key: "duration", text: " flag \"--duration \" help=\"Optional duration in milliseconds for actions with timeouts\""}, {key: "moderator", text: " flag \"-m --moderator \" help=\"The user ID or email of the organization member who made the decision. The action is recorded as theirs.\""}, {key: "create-casebook-case", text: " flag \"--create-casebook-case\" help=\"Record an allow or reject decision as a casebook case. Defaults to true when 'moderator' is set, else false. Without 'moderator', the case is recorded as System.\""}, {key: "body", text: " flag \"--body \" help=\"Request body as JSON (alternative to individual flags). Can also be provided via stdin; @path reads a file, @- reads stdin to EOF. Use --schema to print the exact JSON Schema.\""}, {key: "schema", text: " flag \"--schema\" help=\"Print the exact JSON Schema of the request body and exit\""}}}, "actions list": {head: "cmd \"list\" help=\"List moderation actions\"", flags: []usageItem{{key: "queue-id", text: " flag \"--queue-id \" help=\"string value\""}, {key: "page", text: " flag \"--page \" help=\"integer value\" default=1"}, {key: "page-size", text: " flag \"--page-size \" help=\"integer value\" default=20"}, {key: "project-id", text: " flag \"--project-id \" help=\"The project to act on, by ID or name. Required with an organization key unless the key reaches only one project; list projects with GET /projects. A project key acts on its own project and needs no value: naming another one is an error.\""}, {key: "all", text: " flag \"-a --all\" help=\"Automatically paginate and fetch all results (streams NDJSON for JSON output)\""}, {key: "max-pages", text: " flag \"--max-pages \" help=\"Maximum number of pages to fetch when using --all (0 = no limit)\" default=0"}}}, "actions retrieve": {head: "cmd \"retrieve\" help=\"Get an action\"", args: []usageItem{{key: "id", text: " arg \"id\" help=\"The ID of the action to get. (or use --id)\""}}, flags: []usageItem{{key: "id", text: " flag \"-i --id \" help=\"The ID of the action to get. (or pass it as the [id] argument)\""}, {key: "project-id", text: " flag \"-p --project-id \" help=\"The project to act on, by ID or name. Required with an organization key unless the key reaches only one project; list projects with GET /projects. A project key acts on its own project and needs no value: naming another one is an error.\""}}}, "actions update": {head: "cmd \"update\" help=\"Update an action\"", args: []usageItem{{key: "id", text: " arg \"id\" help=\"The ID of the action to update. (or use --id)\""}}, flags: []usageItem{{key: "id", text: " flag \"-i --id \" help=\"The ID of the action to update. (or pass it as the [id] argument)\""}, {key: "project-id", text: " flag \"--project-id \" help=\"The project to act on, by ID or name. Required with an organization key unless the key reaches only one project; list projects with GET /projects. A project key acts on its own project and needs no value: naming another one is an error.\""}, {key: "key", text: " flag \"-k --key \" help=\"User defined key of the action.\""}, {key: "name", text: " flag \"-n --name \" help=\"The name of the action.\""}, {key: "description", text: " flag \"--description \" help=\"The description of the action.\""}, {key: "type", text: " flag \"-t --type \" help=\"The type of the action. (options: AUTHOR_BLOCK, AUTHOR_BLOCK_TEMP, AUTHOR_UNBLOCK, AUTHOR_DELETE, AUTHOR_REPORT, AUTHOR_WARN, AUTHOR_CUSTOM, ITEM_REJECT, ITEM_ALLOW, ITEM_CUSTOM)\""}, {key: "built-in", text: " flag \"-b --built-in \" help=\"Whether the action is a built-in action or a custom one.\" default=#false"}, {key: "queue-behaviour", text: " flag \"--queue-behaviour \" help=\"Whether the action resolves and removes the item, unresolves and re-add it to the queue, or does not change the resolve status. (options: REMOVE, ADD, NO_CHANGE)\" default=\"NO_CHANGE\""}, {key: "filter-in-queue-ids", text: " flag \"--filter-in-queue-ids \" help=\"The IDs of the queues the action is available in.\" var=#true"}, {key: "position", text: " flag \"--position \" help=\"Show the action in all queues, selected queues or no queues (to use via API only). (options: ALL_QUEUES, SOME_QUEUES, HIDDEN)\" default=\"ALL_QUEUES\""}, {key: "possible-values", text: " flag \"--possible-values \" help=\"The possible values of the action. The user will be prompted to select one of these values when executing the action. (JSON array)\""}, {key: "value-required", text: " flag \"-v --value-required\" help=\"Whether the action requires a value to be executed.\" default=#false"}, {key: "free-text", text: " flag \"--free-text\" help=\"Whether the action allows any text to be entered as a value or if it must be one of the possible values.\" default=#false"}, {key: "body", text: " flag \"--body \" help=\"Request body as JSON (alternative to individual flags). Can also be provided via stdin; @path reads a file, @- reads stdin to EOF. Use --schema to print the exact JSON Schema.\""}, {key: "schema", text: " flag \"--schema\" help=\"Print the exact JSON Schema of the request body and exit\""}}}, @@ -49,11 +49,14 @@ var usageNodes = map[string]usageNode{ "auth login": {head: "cmd \"login\" help=\"Interactively configure authentication credentials\"", flags: []usageItem{{key: "secret-key", text: " flag \"--secret-key \" help=\"HTTP Bearer\" env=\"MODAPI_SECRET_KEY\" config=\"security.secret_key\"", settings: true}}}, "auth logout": {head: "cmd \"logout\" help=\"Clear all stored authentication credentials\""}, "auth whoami": {head: "cmd \"whoami\" help=\"Display current authentication configuration\""}, - "authors": {head: "cmd \"authors\" help=\"Operations for authors\"", children: []string{"create", "delete", "list", "retrieve", "update"}}, + "authors": {head: "cmd \"authors\" help=\"Operations for authors\"", children: []string{"block", "create", "delete", "list", "retrieve", "suspend", "unblock", "update"}}, + "authors block": {head: "cmd \"block\" help=\"Block author\"", args: []usageItem{{key: "id", text: " arg \"id\" help=\"Either external ID or the ID assigned by moderation API. (or use --id)\""}}, flags: []usageItem{{key: "id", text: " flag \"-i --id \" help=\"Either external ID or the ID assigned by moderation API. (or pass it as the [id] argument)\""}, {key: "project-id", text: " flag \"-p --project-id \" help=\"The project to act on, by ID or name. Required with an organization key unless the key reaches only one project; list projects with GET /projects. A project key acts on its own project and needs no value: naming another one is an error.\""}, {key: "reason", text: " flag \"-r --reason \" help=\"Why the action was taken. Saved with the action and sent in the webhook event.\""}, {key: "body", text: " flag \"--body \" help=\"Request body as JSON (alternative to individual flags). Can also be provided via stdin; @path reads a file, @- reads stdin to EOF. Use --schema to print the exact JSON Schema.\""}, {key: "schema", text: " flag \"--schema\" help=\"Print the exact JSON Schema of the request body and exit\""}}}, "authors create": {head: "cmd \"create\" help=\"Create a new author\"", flags: []usageItem{{key: "project-id", text: " flag \"--project-id \" help=\"The project to act on, by ID or name. Required with an organization key unless the key reaches only one project; list projects with GET /projects. A project key acts on its own project and needs no value: naming another one is an error.\""}, {key: "profile-picture", text: " flag \"--profile-picture \" help=\"URL of the author's profile picture\""}, {key: "external-link", text: " flag \"--external-link \" help=\"URL of the author's external profile\""}, {key: "name", text: " flag \"-n --name \" help=\"Author name or identifier\""}, {key: "email", text: " flag \"--email \" help=\"Author email address\""}, {key: "company", text: " flag \"-c --company \" help=\"The author's company or organization\""}, {key: "metadata", text: " flag \"--metadata \" help=\"Additional metadata provided by your system. We recommend including any relevant information that may assist in the moderation process.\""}, {key: "first-seen", text: " flag \"-f --first-seen \" help=\"Timestamp when author first appeared\""}, {key: "last-seen", text: " flag \"-l --last-seen \" help=\"Timestamp of last activity\""}, {key: "manual-trust-level", text: " flag \"--manual-trust-level \" help=\"number value\""}, {key: "external-id", text: " flag \"--external-id \" help=\"External ID of the user, typically the ID of the author in your database. [required]\""}, {key: "body", text: " flag \"--body \" help=\"Request body as JSON (alternative to individual flags). Can also be provided via stdin; @path reads a file, @- reads stdin to EOF. Use --schema to print the exact JSON Schema.\""}, {key: "schema", text: " flag \"--schema\" help=\"Print the exact JSON Schema of the request body and exit\""}}}, "authors delete": {head: "cmd \"delete\" help=\"Delete an author\"", args: []usageItem{{key: "id", text: " arg \"id\" help=\"Either external ID or the ID assigned by moderation API. (or use --id)\""}}, flags: []usageItem{{key: "id", text: " flag \"-i --id \" help=\"Either external ID or the ID assigned by moderation API. (or pass it as the [id] argument)\""}, {key: "project-id", text: " flag \"-p --project-id \" help=\"The project to act on, by ID or name. Required with an organization key unless the key reaches only one project; list projects with GET /projects. A project key acts on its own project and needs no value: naming another one is an error.\""}}}, "authors list": {head: "cmd \"list\" help=\"List authors\"", flags: []usageItem{{key: "page", text: " flag \"--page \" help=\"integer value\" default=1"}, {key: "page-size", text: " flag \"--page-size \" help=\"integer value\" default=20"}, {key: "sort-by", text: " flag \"--sort-by \" help=\"options: trustLevel, violationCount, memberSince, lastActive, contentCount, flaggedContentRatio, averageSentiment\""}, {key: "sort-direction", text: " flag \"--sort-direction \" help=\"Sort direction (options: asc, desc)\" default=\"desc\""}, {key: "member-since-date", text: " flag \"-m --member-since-date \" help=\"string value\""}, {key: "last-active-date", text: " flag \"-l --last-active-date \" help=\"string value\""}, {key: "project-id", text: " flag \"--project-id \" help=\"The project to act on, by ID or name. Required with an organization key unless the key reaches only one project; list projects with GET /projects. A project key acts on its own project and needs no value: naming another one is an error.\""}, {key: "all", text: " flag \"-a --all\" help=\"Automatically paginate and fetch all results (streams NDJSON for JSON output)\""}, {key: "max-pages", text: " flag \"--max-pages \" help=\"Maximum number of pages to fetch when using --all (0 = no limit)\" default=0"}}}, "authors retrieve": {head: "cmd \"retrieve\" help=\"Get author details\"", args: []usageItem{{key: "id", text: " arg \"id\" help=\"Either external ID or the ID assigned by moderation API. (or use --id)\""}}, flags: []usageItem{{key: "id", text: " flag \"-i --id \" help=\"Either external ID or the ID assigned by moderation API. (or pass it as the [id] argument)\""}, {key: "project-id", text: " flag \"-p --project-id \" help=\"The project to act on, by ID or name. Required with an organization key unless the key reaches only one project; list projects with GET /projects. A project key acts on its own project and needs no value: naming another one is an error.\""}}}, + "authors suspend": {head: "cmd \"suspend\" help=\"Suspend author\"", args: []usageItem{{key: "id", text: " arg \"id\" help=\"Either external ID or the ID assigned by moderation API. (or use --id)\""}}, flags: []usageItem{{key: "id", text: " flag \"-i --id \" help=\"Either external ID or the ID assigned by moderation API. (or pass it as the [id] argument)\""}, {key: "project-id", text: " flag \"-p --project-id \" help=\"The project to act on, by ID or name. Required with an organization key unless the key reaches only one project; list projects with GET /projects. A project key acts on its own project and needs no value: naming another one is an error.\""}, {key: "reason", text: " flag \"-r --reason \" help=\"Why the action was taken. Saved with the action and sent in the webhook event.\""}, {key: "duration", text: " flag \"--duration \" help=\"How long the suspension lasts, in milliseconds. Defaults to 24 hours.\""}, {key: "body", text: " flag \"--body \" help=\"Request body as JSON (alternative to individual flags). Can also be provided via stdin; @path reads a file, @- reads stdin to EOF. Use --schema to print the exact JSON Schema.\""}, {key: "schema", text: " flag \"--schema\" help=\"Print the exact JSON Schema of the request body and exit\""}}}, + "authors unblock": {head: "cmd \"unblock\" help=\"Unblock author\"", args: []usageItem{{key: "id", text: " arg \"id\" help=\"Either external ID or the ID assigned by moderation API. (or use --id)\""}}, flags: []usageItem{{key: "id", text: " flag \"-i --id \" help=\"Either external ID or the ID assigned by moderation API. (or pass it as the [id] argument)\""}, {key: "project-id", text: " flag \"-p --project-id \" help=\"The project to act on, by ID or name. Required with an organization key unless the key reaches only one project; list projects with GET /projects. A project key acts on its own project and needs no value: naming another one is an error.\""}, {key: "reason", text: " flag \"-r --reason \" help=\"Why the action was taken. Saved with the action and sent in the webhook event.\""}, {key: "body", text: " flag \"--body \" help=\"Request body as JSON (alternative to individual flags). Can also be provided via stdin; @path reads a file, @- reads stdin to EOF. Use --schema to print the exact JSON Schema.\""}, {key: "schema", text: " flag \"--schema\" help=\"Print the exact JSON Schema of the request body and exit\""}}}, "authors update": {head: "cmd \"update\" help=\"Update author details\"", args: []usageItem{{key: "id", text: " arg \"id\" help=\"Either external ID or the ID assigned by moderation API. (or use --id)\""}}, flags: []usageItem{{key: "id", text: " flag \"-i --id \" help=\"Either external ID or the ID assigned by moderation API. (or pass it as the [id] argument)\""}, {key: "project-id", text: " flag \"--project-id \" help=\"The project to act on, by ID or name. Required with an organization key unless the key reaches only one project; list projects with GET /projects. A project key acts on its own project and needs no value: naming another one is an error.\""}, {key: "profile-picture", text: " flag \"--profile-picture \" help=\"URL of the author's profile picture\""}, {key: "external-link", text: " flag \"--external-link \" help=\"URL of the author's external profile\""}, {key: "name", text: " flag \"-n --name \" help=\"Author name or identifier\""}, {key: "email", text: " flag \"--email \" help=\"Author email address\""}, {key: "company", text: " flag \"-c --company \" help=\"The author's company or organization\""}, {key: "metadata", text: " flag \"--metadata \" help=\"Additional metadata provided by your system. We recommend including any relevant information that may assist in the moderation process.\""}, {key: "first-seen", text: " flag \"-f --first-seen \" help=\"Timestamp when author first appeared\""}, {key: "last-seen", text: " flag \"-l --last-seen \" help=\"Timestamp of last activity\""}, {key: "manual-trust-level", text: " flag \"--manual-trust-level \" help=\"number value\""}, {key: "body", text: " flag \"--body \" help=\"Request body as JSON (alternative to individual flags). Can also be provided via stdin; @path reads a file, @- reads stdin to EOF. Use --schema to print the exact JSON Schema.\""}, {key: "schema", text: " flag \"--schema\" help=\"Print the exact JSON Schema of the request body and exit\""}}}, "channels": {head: "cmd \"channels\" help=\"Operations for channels\"", children: []string{"create", "delete", "duplicate", "list", "retrieve", "update"}}, "channels create": {head: "cmd \"create\" help=\"Create a channel\"", args: []usageItem{{key: "project-id", text: " arg \"project-id\" help=\"The ID of the project to add the channel to. (or use --project-id)\""}}, flags: []usageItem{{key: "project-id", text: " flag \"-p --project-id \" help=\"The ID of the project to add the channel to. (or pass it as the [project-id] argument)\""}, {key: "name", text: " flag \"-n --name \" help=\"The channel's name. [required]\""}, {key: "key", text: " flag \"-k --key \" help=\"The key you pass as 'channel' when you moderate content. Defaults to the name in lowercase with dashes. Must be unique in the project.\""}, {key: "description", text: " flag \"--description \" help=\"The channel's description.\""}, {key: "content-type", text: " flag \"-c --content-type \" help=\"The kind of content the channel moderates. (options: profile, message, post, comment, event, product, review, voice, other)\""}, {key: "body", text: " flag \"--body \" help=\"Request body as JSON (alternative to individual flags). Can also be provided via stdin; @path reads a file, @- reads stdin to EOF. Use --schema to print the exact JSON Schema.\""}, {key: "schema", text: " flag \"--schema\" help=\"Print the exact JSON Schema of the request body and exit\""}}}, @@ -68,7 +71,9 @@ var usageNodes = map[string]usageNode{ "completion powershell": {head: "cmd \"powershell\" help=\"Generate the autocompletion script for powershell\"", flags: []usageItem{{key: "no-descriptions", text: " flag \"--no-descriptions\" help=\"disable completion descriptions\""}}}, "completion zsh": {head: "cmd \"zsh\" help=\"Generate the autocompletion script for zsh\"", flags: []usageItem{{key: "no-descriptions", text: " flag \"--no-descriptions\" help=\"disable completion descriptions\""}}}, "configure": {head: "cmd \"configure\" help=\"Configure authentication credentials and preferences\"", flags: []usageItem{{key: "default-output-format", text: " flag \"--default-output-format \" help=\"Store the default output format without opening the form. Options: pretty, json, yaml, table, toon. Pass an empty value to clear it.\" {\n suggestions \"pretty\" \"json\" \"yaml\" \"table\" \"toon\"\n }"}, {key: "secret-key", text: " flag \"--secret-key \" help=\"HTTP Bearer\" env=\"MODAPI_SECRET_KEY\" config=\"security.secret_key\"", settings: true}}}, - "content": {head: "cmd \"content\" help=\"Operations for content\"", children: []string{"submit"}}, + "content": {head: "cmd \"content\" help=\"Operations for content\"", children: []string{"allow", "reject", "submit"}}, + "content allow": {head: "cmd \"allow\" help=\"Allow content\"", args: []usageItem{{key: "id", text: " arg \"id\" help=\"The content ID you sent when you submitted the content, or the ID Moderation API assigned. (or use --id)\""}}, flags: []usageItem{{key: "id", text: " flag \"-i --id \" help=\"The content ID you sent when you submitted the content, or the ID Moderation API assigned. (or pass it as the [id] argument)\""}, {key: "project-id", text: " flag \"-p --project-id \" help=\"The project to act on, by ID or name. Required with an organization key unless the key reaches only one project; list projects with GET /projects. A project key acts on its own project and needs no value: naming another one is an error.\""}, {key: "reason", text: " flag \"-r --reason \" help=\"Why the action was taken. Saved with the action and sent in the webhook event.\""}, {key: "queue-id", text: " flag \"--queue-id \" help=\"The queue the content is reviewed in. Defaults to the project's default queue.\""}, {key: "moderator", text: " flag \"-m --moderator \" help=\"The user ID or email of the organization member who made the decision. The action is recorded as theirs.\""}, {key: "create-casebook-case", text: " flag \"-c --create-casebook-case\" help=\"Record an allow or reject decision as a casebook case. Defaults to true when 'moderator' is set, else false. Without 'moderator', the case is recorded as System.\""}, {key: "body", text: " flag \"--body \" help=\"Request body as JSON (alternative to individual flags). Can also be provided via stdin; @path reads a file, @- reads stdin to EOF. Use --schema to print the exact JSON Schema.\""}, {key: "schema", text: " flag \"--schema\" help=\"Print the exact JSON Schema of the request body and exit\""}}}, + "content reject": {head: "cmd \"reject\" help=\"Reject content\"", args: []usageItem{{key: "id", text: " arg \"id\" help=\"The content ID you sent when you submitted the content, or the ID Moderation API assigned. (or use --id)\""}}, flags: []usageItem{{key: "id", text: " flag \"-i --id \" help=\"The content ID you sent when you submitted the content, or the ID Moderation API assigned. (or pass it as the [id] argument)\""}, {key: "project-id", text: " flag \"-p --project-id \" help=\"The project to act on, by ID or name. Required with an organization key unless the key reaches only one project; list projects with GET /projects. A project key acts on its own project and needs no value: naming another one is an error.\""}, {key: "reason", text: " flag \"-r --reason \" help=\"Why the action was taken. Saved with the action and sent in the webhook event.\""}, {key: "queue-id", text: " flag \"--queue-id \" help=\"The queue the content is reviewed in. Defaults to the project's default queue.\""}, {key: "moderator", text: " flag \"-m --moderator \" help=\"The user ID or email of the organization member who made the decision. The action is recorded as theirs.\""}, {key: "create-casebook-case", text: " flag \"-c --create-casebook-case\" help=\"Record an allow or reject decision as a casebook case. Defaults to true when 'moderator' is set, else false. Without 'moderator', the case is recorded as System.\""}, {key: "body", text: " flag \"--body \" help=\"Request body as JSON (alternative to individual flags). Can also be provided via stdin; @path reads a file, @- reads stdin to EOF. Use --schema to print the exact JSON Schema.\""}, {key: "schema", text: " flag \"--schema\" help=\"Print the exact JSON Schema of the request body and exit\""}}}, "content submit": {head: "cmd \"submit\" help=\"Submit content\"", flags: []usageItem{{key: "project-id", text: " flag \"--project-id \" help=\"The project to act on, by ID or name. Required with an organization key unless the key reaches only one project; list projects with GET /projects. A project key acts on its own project and needs no value: naming another one is an error.\""}, {key: "content", text: " flag \"--content \" help=\"{ \\\"text\\\": string } | { \\\"url\\\": string, \\\"data\\\": string } | { \\\"url\\\": string } | { \\\"data\\\": object }\""}, {key: "content.text", text: " flag \"--content.text \" help=\"Text_request variant as JSON\""}, {key: "content.text.text", text: " flag \"--content.text.text \" help=\"The content text [required]\""}, {key: "content.image", text: " flag \"--content.image \" help=\"Image_request variant as JSON\""}, {key: "content.image.url", text: " flag \"--content.image.url \" help=\"A public URL of the image content. Either url or data must be provided.\""}, {key: "content.image.data", text: " flag \"--content.image.data \" help=\"Base64-encoded image data. Either url or data must be provided. Note: base64 images are not stored and will not appear in the review queue.\""}, {key: "content.video", text: " flag \"--content.video