diff --git a/.github/workflows/go-test.yml b/.github/workflows/go-test.yml new file mode 100644 index 0000000..b77d9d5 --- /dev/null +++ b/.github/workflows/go-test.yml @@ -0,0 +1,31 @@ +name: Upload Go test results +# https://docs.github.com/en/actions/use-cases-and-examples/building-and-testing/building-and-testing-go +on: [push] +permissions: + contents: read + +jobs: + build: + + runs-on: ubuntu-latest + strategy: + matrix: + go-version: [ '1.24.3' ] + + steps: + - uses: actions/checkout@v4 + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: ${{ matrix.go-version }} + - name: Install dependencies + run: go get . + working-directory: ./src + - name: Test with Go + run: go test -json > TestResults-${{ matrix.go-version }}.json + working-directory: ./src + - name: Upload Go test results + uses: actions/upload-artifact@v4 + with: + name: Go-results-${{ matrix.go-version }} + path: src/TestResults-${{ matrix.go-version }}.json diff --git a/README.md b/README.md index a04e13d..9d25b06 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # e6-cache -**e6-cache** is a locally hosted caching proxy for e621 (or anything that's api compatible), designed to passively archive and cache content you browse. +**e6-cache** is a locally hosted caching proxy for e621 (or anything that's api compatible), designed to passively archive and cache content as you browse. ## Why? @@ -9,7 +9,7 @@ Because: * You don’t remember that *one* post with the perfect lighting and suspiciously specific tags * **You want your own archive. Your own CDN. Your own sin-server.** * You have too much storage space -* Your Internet connection is too slow (the only legitimate use case) +* Your Internet connection is too slow * You worry about e621 going down, or posts being removed. ## What it does @@ -24,7 +24,7 @@ Because: * **Transparent Proxy**: Redirects all requests through e6-cache, then to your chosen instance. * **Passive Caching**: Automatically caches every post you view. * **Local Storage**: Stores metadata in a local PostgreSQL database and media files in your own S3-compatible bucket. -* **Customizable**: Easily configurable to work with any e621-compatible instance. +* **Fast**: Streams the images / videos directly to your client. * **Self-Hosted**: Runs on your own server, giving you full control over your data. * **Authentication**: Supports authentication for secure access, even when exposed to the world. @@ -64,20 +64,20 @@ After the container is running, you can access the API at `http://localhost:8080 For most users i recommend using [e1547](https://github.com/clragon/e1547) as it has built-in support for custom instances. -### e1547 Setup +### e1547 > [!IMPORTANT] > You need to have an public URL for the API to work with e1547, as it requires https. https://github.com/user-attachments/assets/d2304e64-0c08-4065-bd55-aaa24d13727e -### The Wolf's Stash Setup +### The Wolf's Stash For The Wolf's Stash, it just reports the host not being supported. ### Other Clients Feel free to open a PR to add documentation for other clients. -## Speed Comparison (Speed depends on your internet, and database speed) +## Speed Comparison ### Image 1: - **Normal e621 image load:** 2.859s @@ -94,21 +94,9 @@ Feel free to open a PR to add documentation for other clients. * Proxy Mode (it act's like a proxy and redirects all e621 requests to e6-cache) * Firefox Extension (to make it easier to use by replacing all e621 links with e6-cache links) -* Offline Mode (to use the cache without internet connection, like a local e621) +* Offline API Mode (to use the cache without internet connection, like a local e621) * Website (basically a mirror of e621, but with the cache enabled) -## Architecture - -* Every API request you make goes through `e6-cache` -* We pull the data from the API and store it in our database. And then we serve it to you -* If there are images, we create special proxy links -* If the user or a client accesses the proxy link, we check if the image is already cached, and if not, we download it and store it in the S3 bucket - -## Ethical & Legal Notice - -This project **does not** scrape, spider, or hammer the API. It only caches what *you* manually request. -You, as the operator, are fully responsible for your usage. This just hands you a shovel. What you dig up is on you. - ## Contributing Feel free to open an issue or a pull request. I don't have any specific guidelines for contributing, just be nice and respectful. @@ -116,3 +104,8 @@ Feel free to open an issue or a pull request. I don't have any specific guidelin ## Why? Idk. I just wanted to play with Go, Docker and wanted to make something (relatively) useful. It's also my first published project on GitHub. + +## Ethical & Legal Notice + +This project **does not** scrape, spider, or hammer the API. It only caches what *you* manually request. +You, as the operator, are fully responsible for your usage. This just hands you a shovel. What you dig up is on you. \ No newline at end of file diff --git a/THIRD_PARTY.md b/THIRD_PARTY.md new file mode 100644 index 0000000..9d4df6c --- /dev/null +++ b/THIRD_PARTY.md @@ -0,0 +1,5 @@ +# Third Party Libraries / Assets + +This project wouldn't be possible without the following third party libraries and assets: + +- e621 OpenAPI Specification from [https://github.com/DonovanDMC/E621OpenAPI](https://github.com/DonovanDMC/E621OpenAPI) (License: MIT) \ No newline at end of file diff --git a/development.md b/development.md new file mode 100644 index 0000000..3964319 --- /dev/null +++ b/development.md @@ -0,0 +1,23 @@ + +## Development +`e6-cache` works by forwarding the users request, which means that it should never require any client changes, and should work on anything that abides by the offical api. + +## Request Forwarding Process +The core concept of the implementation looks like this: + +1. Receive an api request +2. Forward the request to the target e6-based service (While checking for the Proxy Auth for example) +3. Capture the response +4. Modify the response and save it in the DB (URIs dont change in the DB) +5. Return the modified response to the client + +## File Proxying Process +File Proxying works like this: + +1. Check the Signature and decode the base64 encoded url +2. Check in S3 if the file exists +3. If not, then request it and save it while forwarding it to the client. If it exist than stream it to the client from S3. + +## OpenAPI Updates +The `update_openapi.sh` script: +- Updates the openai.yaml file from another repo \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index ed29f13..97b4571 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -41,7 +41,7 @@ services: # Proxy settings PROXY_URL: http://localhost:8080 # Set this to the Server IP / URL, as otherwise the proxy will not work. E6_BASE: https://e621.net - PROXY_AUTH: "" # Leave empty to disable proxy auth append like this to your username "Username:YourPassword" + PROXY_AUTH: "" # Leave empty to disable proxy auth. If you want to use it, append like this to your username "Username:YourProxyPassword" ports: - "8080:8080" # Point this to an Reverse Proxy and set the Proxy Url acordingly. diff --git a/src/.env.example b/src/.env.example index 7954167..58c5cc5 100644 --- a/src/.env.example +++ b/src/.env.example @@ -17,4 +17,4 @@ S3_REGION=us-east-1 # Proxy settings PROXY_URL=http://localhost:8080 E6_BASE=https://e621.net -PROXY_AUTH="" # Leave empty to disable proxy auth append like this to your username "Username:YourPassword" \ No newline at end of file +PROXY_AUTH="" # Leave empty to disable proxy auth. If you want to use it, append like this to your username "Username:YourProxyPassword" \ No newline at end of file diff --git a/src/db.go b/src/db.go index 222fc6c..fed58f6 100644 --- a/src/db.go +++ b/src/db.go @@ -5,7 +5,6 @@ import ( "context" "database/sql" "fmt" - "log" "strings" "time" @@ -80,23 +79,23 @@ func (d *DB) CreatePost(ctx context.Context, p *Post) error { ) if err != nil { - logging.Error("Error inserting post: ", err) + logging.Error("Error inserting post: %v", err) } return err } func (d *DB) CheckAndInsertPost(ctx context.Context, p *Post) error { - logging.Info("Checking if post exists: ", p.ID) + logging.Info("Checking if post exists: %v", p.ID) const existsQuery = `SELECT 1 FROM posts WHERE id = $1` row := d.db.QueryRowContext(ctx, existsQuery, p.ID) var dummy int err := row.Scan(&dummy) switch { case err == sql.ErrNoRows: - logging.Info("Post does not exist, inserting: ", p.ID) + logging.Error("Post does not exist, inserting: %v", p.ID) return d.CreatePost(ctx, p) case err != nil: - log.Println("Error checking post existence: ", err) + logging.Error("Error checking post existence: %v", err) return err default: // Row exists, nothing to do @@ -143,7 +142,7 @@ func (d *DB) SaveComments(comments []Comment) error { c.UpdaterName, ) if err != nil { - logging.Error(err.Error()) + logging.Error("%v", err.Error()) return err } } @@ -227,7 +226,7 @@ func (d *DB) UpdatePost(ctx context.Context, p *Post) error { p.ApproverID, p.UploaderID, p.Description, p.CommentCount, p.IsFavorited, ) if err != nil { - logging.Error("Error updating post: ", err) + logging.Error("Error updating post: %v", err) } return err } @@ -236,7 +235,7 @@ func (d *DB) UpdatePost(ctx context.Context, p *Post) error { func (d *DB) DeletePost(ctx context.Context, id int64) error { _, err := d.db.ExecContext(ctx, `DELETE FROM posts WHERE id = $1`, id) if err != nil { - logging.Error("Error deleting post: ", err) + logging.Error("Error deleting post: %v", err) } return err } @@ -271,20 +270,20 @@ func (d *DB) UpdatePool(ctx context.Context, p *Pool) error { p.Description, p.IsActive, p.Category, p.PostCount, ) if err != nil { - logging.Error("error upserting pool: ", err) + logging.Error("error upserting pool: %v", err) return err } _, err = tx.ExecContext(ctx, `DELETE FROM pool_posts WHERE pool_id = $1`, p.ID) if err != nil { - logging.Error("error clearing pool_posts: ", err) + logging.Error("error clearing pool_posts: %v", err) return err } for _, postID := range p.PostIDs { _, err = tx.ExecContext(ctx, `INSERT INTO pool_posts (pool_id, post_id) VALUES ($1, $2)`, p.ID, postID) if err != nil { - logging.Error("error inserting pool_post: ", err) + logging.Error("error inserting pool_post: %v", err) return err } } diff --git a/src/go.mod b/src/go.mod index 26b1c71..8a22778 100644 --- a/src/go.mod +++ b/src/go.mod @@ -9,6 +9,7 @@ require ( github.com/aws/aws-sdk-go-v2/credentials v1.17.67 github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.76 github.com/aws/aws-sdk-go-v2/service/s3 v1.79.4 + github.com/getkin/kin-openapi v0.132.0 github.com/gin-gonic/gin v1.10.1 github.com/joho/godotenv v1.5.1 github.com/lib/pq v1.10.9 @@ -35,17 +36,25 @@ require ( github.com/cloudwego/iasm v0.2.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/swag v0.23.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.20.0 // indirect github.com/goccy/go-json v0.10.2 // indirect + github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/leodido/go-urn v1.4.0 // indirect + github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect + github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/perimeterx/marshmallow v1.1.5 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect golang.org/x/arch v0.8.0 // indirect diff --git a/src/go.sum b/src/go.sum index 11bfdba..a1730ed 100644 --- a/src/go.sum +++ b/src/go.sum @@ -51,10 +51,16 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/getkin/kin-openapi v0.132.0 h1:3ISeLMsQzcb5v26yeJrBcdTCEQTag36ZjaGk7MIRUwk= +github.com/getkin/kin-openapi v0.132.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ= github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= @@ -70,6 +76,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= @@ -80,6 +88,8 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -87,8 +97,16 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw= +github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c= +github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= +github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/src/http_calls.go b/src/http_calls.go index aa53411..1fa1934 100644 --- a/src/http_calls.go +++ b/src/http_calls.go @@ -10,6 +10,7 @@ import ( "context" "encoding/base64" "encoding/json" + "fmt" "io" "net/http" "path/filepath" @@ -46,15 +47,14 @@ func makeProxyLink(original string) string { // if the route changes, we need to update this proxiedURL := PROXY_URL + "/proxy/" + encodedUrl + "?sig=" + sig - - logging.Info("Creating proxy url for file: ", original, " | ID: ", match[1], " | Proxied URL: ", proxiedURL) + logging.Info("Creating proxy url for file: %v | ID: %v | Proxied URL: %v", original, match[1], proxiedURL) return proxiedURL } func proxyAndTransform(c *gin.Context) { - logging.Debug("Headers: ", c.Request.Header) + logging.Debug("Headers: %v", c.Request.Header) auth := c.Request.Header.Get("Authorization") @@ -72,13 +72,14 @@ func proxyAndTransform(c *gin.Context) { auth = strings.TrimPrefix(auth, "Basic ") decodedAuth, err := base64.StdEncoding.DecodeString(auth) if err != nil { - logging.Error("Error decoding auth header: ", err) + logging.Error("Error decoding auth header: %v", err) c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) return } // parse it into username, password and proxy auth authParts := strings.Split(string(decodedAuth), ":") - suppliedProxyAuth := authParts[1] // if you want to check the password, set it to 2 + suppliedProxyAuth := authParts[1] // to change the password position you need to change this here. 1 is after the username, 2 is after the proxy auth + logging.Debug("Parsed Proxy Authorization header: %v", authParts) if len(authParts) != 3 || suppliedProxyAuth != PROXY_AUTH { c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) return @@ -86,8 +87,9 @@ func proxyAndTransform(c *gin.Context) { c.Request.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(authParts[0]+":"+authParts[2]))) } else { - + // this gets run if the proxy auth function not enabled if auth == "" { + logging.Debug("No Authorization header found, using anonymous user") requestUsername = "anonymous" } @@ -124,8 +126,8 @@ func proxyAndTransform(c *gin.Context) { // useragent stuff setUseragent(requestUsername, req) - logging.Debug("Host: ", c.Request.Host) - logging.Debug("Proxied Headers: ", req.Header) + logging.Debug("Host: %v", c.Request.Host) + logging.Debug("Proxied Headers: %v", req.Header) // Perform request resp, err := http.DefaultClient.Do(req) @@ -172,11 +174,10 @@ func proxyAndTransform(c *gin.Context) { defer reader.Close() default: + logging.Debug("Server sent uncompressed response") reader = resp.Body } - //Accept-Encoding:[gzip, compress, deflate, br] - // Read response body respBody, err := io.ReadAll(reader) @@ -184,13 +185,14 @@ func proxyAndTransform(c *gin.Context) { c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": "Failed to read response body"}) return } + // check for rate limit if resp.StatusCode == 501 { c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{"error": "Rate limit exceeded"}) return } - logging.Debug("Response Body: ", string(respBody)) + logging.Debug("Response Body: %v", string(respBody)) switch { case strings.HasSuffix(c.Request.URL.Path, "/comments.json") && c.Query("search[post_id]") != "": // specific post comments are returned differently @@ -208,14 +210,14 @@ func proxyAndTransform(c *gin.Context) { return } - logging.Info("Saving ", len(comments), " comments") + logging.Info("Saving %v comments", len(comments)) Database.SaveComments(comments) respBody, _ = json.Marshal(comments) case strings.HasSuffix(c.Request.URL.Path, "/posts.json") || strings.HasSuffix(c.Request.URL.Path, "/comments.json"): // comments and posts seem to be the same thing var posts PostsResponse if err := json.Unmarshal(respBody, &posts); err != nil { - logging.Debug("Response Body: ", string(respBody)) + logging.Debug("Response Body: %v", string(respBody)) c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid response format", "ok": false}) return } @@ -230,7 +232,7 @@ func proxyAndTransform(c *gin.Context) { if err := json.Unmarshal(respBody, &post); err != nil { - logging.Debug("Response Body: ", string(respBody)) + logging.Debug("Response Body: %v", string(respBody)) c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid response format", "ok": false}) return } @@ -248,7 +250,7 @@ func proxyAndTransform(c *gin.Context) { for _, pool := range pools { // Store in DB - ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), globalTimeout) defer cancel() Database.UpdatePool(ctx, &pool) } @@ -263,7 +265,7 @@ func proxyAndTransform(c *gin.Context) { } // Store in DB - ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), globalTimeout) defer cancel() Database.UpdatePool(ctx, &pool) @@ -275,7 +277,7 @@ func proxyAndTransform(c *gin.Context) { } func proxyFile(c *gin.Context) { - fileID := c.Param("File_ID") + fileID := c.Param("fileId") sig := c.Query("sig") if sig == "" { @@ -305,11 +307,11 @@ func proxyFile(c *gin.Context) { c.Header("Expires", time.Now().Add(time.Duration(maxCacheAge)*time.Second).Format(http.TimeFormat)) if fileExists && err == nil { - logging.Info("File exists in S3, downloading: ", string(url)) + logging.Info("File exists in S3, downloading: %v", string(url)) body, err := S3.StreamFromS3(c, string(CleanFileID)) if err != nil { - logging.Error("Error downloading from S3: ", err) + logging.Error("Error downloading from S3: %v", err) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": "Failed to download from S3", "ok": false}) return } @@ -383,12 +385,12 @@ func proxyFile(c *gin.Context) { // upload to S3 in the background, while the user is downloading the file go func() { - logging.Info("Uploading to S3: ", string(CleanFileID)) + logging.Info("Uploading to S3: %v", string(CleanFileID)) err := S3.UploadToS3(c, r1, string(CleanFileID)) if err != nil { - logging.Error("Failed to upload to S3", err) + logging.Error("Failed to upload to S3: %v", err) } - logging.Info("Upload to S3 complete: ", string(CleanFileID)) + logging.Info("Upload to S3 complete: %v", string(CleanFileID)) }() // Stream live to user (I hope it's actually streaming) @@ -416,11 +418,11 @@ func copyHeaders(src http.Header, dst http.Header) { } func ProcessPost(c *gin.Context, post *Post) { - ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), globalTimeout) defer cancel() Database.CheckAndInsertPost(ctx, post) - // Rewrite url to go through proxy + // Rewrite url to go through the proxy post.File.URL = makeProxyLink(post.File.URL) post.Preview.URL = makeProxyLink(post.Preview.URL) post.Sample.URL = makeProxyLink(post.Sample.URL) @@ -434,7 +436,7 @@ func setUseragent(username string, req *http.Request) { return } - useragent = useragentBase + " (Request made on behalf of " + username + ")" + useragent = fmt.Sprintf("%s (Request made on behalf of %s)", useragentBase, username) req.Header.Set("User-Agent", useragent) } diff --git a/src/main.go b/src/main.go index 05f1285..e4e04e6 100644 --- a/src/main.go +++ b/src/main.go @@ -4,12 +4,19 @@ import ( "bugmaschine/e6-cache/logging" "context" _ "embed" + "fmt" + "log" "os" + "regexp" + "slices" "strconv" + "strings" "time" "bugmaschine/e6-cache/signer" + "github.com/getkin/kin-openapi/openapi3" + "github.com/gin-gonic/gin" "github.com/joho/godotenv" ) @@ -23,9 +30,10 @@ var ( Database DB useragentBase = "e6-cache (https://github.com/bugmaschine/e6-cache)" port = ":8080" - Key []byte // gets randomly generated every launch, and used for signing the urls. - maxCacheAge = 1 * time.Hour // idk what's a good value, but 1 hours seems enough - Signer *signer.Signer // feel free to sugest a better name + Key []byte // gets randomly generated every launch, and used for signing the urls. + maxCacheAge = 1 * time.Hour // idk what's a good value, but 1 hours seems enough + Signer *signer.Signer // feel free to sugest a better name + globalTimeout = 5 * time.Second // global timeout for requests to e6, if it takes longer than this, we assume the request failed. // env stuff @@ -48,6 +56,9 @@ var ( PROXY_URL string baseURL string PROXY_AUTH string + + //go:embed "openapi/e621.yaml" + e621OpenApiRoutes []byte // embedded OpenAPI routes, used to dynamically register the routes in the gin router. ) func loadEnv() { @@ -78,9 +89,9 @@ func loadEnv() { PROXY_AUTH = os.Getenv("PROXY_AUTH") if PROXY_AUTH != "" { - logging.Debug("Proxy auth is enabled with key: ", PROXY_AUTH) + logging.Info("Proxy auth is enabled with key: %v", PROXY_AUTH) } else { - logging.Debug("Proxy auth is disabled") + logging.Info("Proxy auth is disabled") } } @@ -94,13 +105,13 @@ func main() { // generate signing key Key = signer.GenerateSecretKey() Signer = signer.NewSigner(Key) - logging.Debug("Generated key: ", Key) + logging.Debug("Generated key: %v", Key) // setup db logging.Info("Connecting to DB...") d, err := newDB(DB_HOST, DB_NAME, DB_USER, DB_PASS, DB_PORT) if err != nil { - logging.Info("Failed to connect to DB (is it up?): ", err) + logging.Info("Failed to connect to DB (is it up?): %v", err) return } Database = d @@ -108,11 +119,11 @@ func main() { // setup s3 logging.Info("Connecting to S3...") - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), globalTimeout) defer cancel() s3Svc, err := NewS3Service(ctx, S3_REGION, S3_ENDPOINT, S3_ACCESS_KEY, S3_SECRET_KEY, S3_BUCKET_NAME) if err != nil { - logging.Fatal("Failed to connect to S3: ", err) + logging.Fatal("Failed to connect to S3: %v", err) } S3 = *s3Svc logging.Info("Connected to S3!") @@ -124,47 +135,11 @@ func main() { gin.SetMode(gin.ReleaseMode) } - router.ForwardedByClientIP = true - - // Routes implementing caching - - router.GET("/posts.json", proxyAndTransform) - router.GET("/posts/:Post_ID.json", proxyAndTransform) - router.GET("/pools.json", proxyAndTransform) - router.GET("/pools/:Pool_ID", proxyAndTransform) - router.GET("/comments.json", proxyAndTransform) // this for reason just returns posts????????? - - // Routes that could be maybe saved - - router.GET("/notes.json", proxyAndTransform) - router.GET("/wiki_pages.json", proxyAndTransform) - router.GET("/post_flags.json", proxyAndTransform) - - // Routes just needing proxying and have make no sense being cached - - router.POST("/favorites.json", proxyAndTransform) - router.POST("/uploads.json", proxyAndTransform) - router.POST("/post_flags.json", proxyAndTransform) - router.DELETE("/favorites/:Post_ID", proxyAndTransform) - router.POST("/notes.json", proxyAndTransform) - router.PUT("/notes/*path", proxyAndTransform) - router.DELETE("/notes/:Note_ID", proxyAndTransform) - router.POST("/posts/:Post_ID/votes.json", proxyAndTransform) - router.GET("/forum_topics.json", proxyAndTransform) - router.GET("/forum_posts.json", proxyAndTransform) - router.POST("/comments.json", proxyAndTransform) - router.PUT("/comments/:Comment_ID", proxyAndTransform) - router.DELETE("/comments/:Comment_ID", proxyAndTransform) - router.POST("/comments/:Comment_ID/votes.json", proxyAndTransform) - router.PUT("/users/:User_ID.json", proxyAndTransform) - router.GET("/users/:User_ID.json", proxyAndTransform) - router.GET("/tags/*path", proxyAndTransform) - - // potentially just forward to the baseUrl - router.PATCH("/posts/:Post_ID", proxyAndTransform) + // register e621 routes + parseOpenAPIRoutes(e621OpenApiRoutes, router) // Proxy files from S3, if not save them. - router.GET("/proxy/:File_ID", proxyFile) + router.GET("/proxy/:fileId", proxyFile) router.GET("/", func(c *gin.Context) { c.String(200, "e6-cache is running. Use this as the instance in your preffered client.\n"+ @@ -172,7 +147,69 @@ func main() { "Server is caching following url: "+baseURL) }) - logging.Info("Started router at ", port) + logging.Info("Started router at %v", port) router.Run(port) +} + +func parseOpenAPIRoutes(openapifile []byte, router *gin.Engine) { + + // load all routes from the file + loader := openapi3.NewLoader() + doc, err := loader.LoadFromData(openapifile) + if err != nil { + log.Fatalf("Failed to load OpenAPI: %v", err) + } + + // create a regex to convert OpenAPI path parameters {id} to Gin's format :id + re := regexp.MustCompile(`\{(.+?)\}`) + + // regex to remove a file extension from the end like .json, .png, etc. + extRegex := regexp.MustCompile(`\.[a-zA-Z0-9]+$`) + + var registeredRoutes []string + + for _, path := range doc.Paths.InMatchingOrder() { + pathItem := doc.Paths.Find(path) + if pathItem == nil { + logging.Warn("Path item not found for path: %v", path) + continue + } + logging.Debug("Processing path: %v", path) + + // convert OpenAPI parameter syntax to Gin parameter syntax + convertedPath := re.ReplaceAllString(path, ":$1") + + // get the parameter for later use + matches := re.FindStringSubmatch(path) + var param string + if len(matches) > 1 { + param = fmt.Sprintf(":%s", matches[1]) // this is $1 + } + + // remove leading slash + convertedPath = strings.TrimPrefix(convertedPath, "/") + + // only remove the extension if it's not a parameter, otherwise it will break the route + // this is here to preserve cases like /posts.json + if strings.HasSuffix(extRegex.ReplaceAllString(convertedPath, ""), param) && param != "" { + convertedPath = extRegex.ReplaceAllString(convertedPath, "") + logging.Debug("Removed extension from path: %v", convertedPath) + } else { + logging.Debug("Preserving extension in path: %v because it's a parameter or doesn't have an extension", convertedPath) + } + + if slices.Contains(registeredRoutes, convertedPath) { + logging.Warn("Duplicate route detected: %v", convertedPath) + continue + } + + registeredRoutes = append(registeredRoutes, convertedPath) + for method := range pathItem.Operations() { + logging.Debug("Adding route: %v %v", method, convertedPath) + router.Handle(method, convertedPath, proxyAndTransform) + } + } + + logging.Info("Registered %d routes from OpenAPI spec", len(doc.Paths.InMatchingOrder())) } diff --git a/src/openapi/e621.yaml b/src/openapi/e621.yaml new file mode 100644 index 0000000..8afb089 --- /dev/null +++ b/src/openapi/e621.yaml @@ -0,0 +1,11428 @@ +openapi: "3.1.0" +info: + title: "E621" + description: | + OpenAPI definition for E621's API. You can find the source [here](https://github.com/DonovanDMC/E621OpenAPI)
+ This document is intended to compliment E621's existing [API Documentation](https://e621.net/help/api).
+ Note if E621's api is under attack and/or cloudflare protections are enabled, the "Try it out" buttons here will not work.
+ If they are not working, you can check this [Unofficial Status Page](https://status.e621.ws). + license: + name: "MIT" + url: "https://opensource.org/licenses/MIT" + + contact: + name: Donovan_DMC + url: https://e621.net/forum_topics/46279 + version: "d69c34e" + +servers: + - url: https://e621.net + description: E621 (production) + - url: http://localhost:3000 + description: E621 (Development) + +x-constants: + - &modaction-actions + - artist_page_rename + - artist_page_lock + - artist_page_unlock + - artist_user_linked + - artist_user_unlinked + - avoid_posting_create + - avoid_posting_update + - avoid_posting_delete + - avoid_posting_undelete + - avoid_posting_destroy + - blip_delete + - blip_hide + - blip_unhide + - blip_update + - comment_delete + - comment_hide + - comment_unhide + - comment_update + - forum_category_create + - forum_category_delete + - forum_category_update + - forum_post_delete + - forum_post_hide + - forum_post_unhide + - forum_post_update + - forum_topic_delete + - forum_topic_hide + - forum_topic_unhide + - forum_topic_lock + - forum_topic_unlock + - forum_topic_stick + - forum_topic_unstick + - forum_topic_update + - help_create + - help_delete + - help_update + - ip_ban_create + - ip_ban_delete + - mascot_create + - mascot_update + - mascot_delete + - pool_delete + - report_reason_create + - report_reason_delete + - report_reason_update + - set_update + - set_delete + - set_change_visibility + - tag_alias_create + - tag_alias_update + - tag_implication_create + - tag_implication_update + - ticket_claim + - ticket_unclaim + - ticket_update + - upload_whitelist_create + - upload_whitelist_update + - upload_whitelist_delete + - user_blacklist_changed + - user_text_change + - user_upload_limit_change + - user_flags_change + - user_level_change + - user_name_change + - user_delete + - user_ban + - user_ban_update + - user_unban + - user_feedback_create + - user_feedback_update + - user_feedback_delete + - user_feedback_undelete + - user_feedback_destroy + - wiki_page_rename + - wiki_page_delete + - wiki_page_lock + - wiki_page_unlock + - mass_update + - nuke_tag + - takedown_delete + - takedown_process + # legacy + - created_positive_record + - created_neutral_record + - created_negative_record + - created_flag_reason + - edited_flag_reason + - deleted_flag_reason + - post_move_favorites + - post_delete + - post_undelete + - post_destroy + - post_rating_lock + - post_unapprove + - post_replacement_accept + - post_replacement_reject + - post_replacement_delete + - &post-event-actions + - deleted + - undeleted + - approved + - unapproved + - flag_created + - flag_removed + - favorites_moved + - favorites_received + - rating_locked + - rating_unlocked + - status_locked + - status_unlocked + - note_locked + - note_unlocked + - comment_locked + - comment_unlocked + - replacement_accepted + - replacement_rejected + - replacement_promoted + - replacement_deleted + - expunged + - changed_bg_color + - &ratings + - s + - q + - e + - &tag-categories + - 0 # general + - 1 # artist + - 3 # copyright + - 4 # character + - 5 # species + - 6 # invalid + - 7 # meta + - 8 # lore + - &feedback-categories + - negative + - neutral + - positive + - &tag-request-statuses + - active + - deleted + - processing + - queued + - retired + - error + - pending + - &warning-types + - warning + - record + - ban + - &pool-categories + - collection + - series + - &ticket-types + - blip + - comment + - dmail + - forum + - pool + - post + - set + - user + - wiki + - &ticket-statuses + - pending + - partial + - approved + +components: + securitySchemes: + basicAuth: + type: http + scheme: basic + parameters: + id: + name: search[id] + in: query + description: Search for a specific id. + schema: + type: number + limit: + name: limit + in: query + description: The maximum number of results to return. Between 0 and 320. + schema: + type: number + minimum: 0 + maximum: 500 + page: + name: page + in: query + description: The page number of results to get. Between 1 and 750. + schema: + type: number + minimum: 1 + maximum: 750 + ip_addr: + name: search[ip_addr] + in: query + description: Must be Admin+ to use. See [postgres' documentation](https://www.postgresql.org/docs/9.3/functions-net.html) for information on how this is parsed. Specifically, "is contained within or equals" (`<<=`). + schema: + type: string + schemas: + Artist: + type: object + required: + - id + - name + - updated_at + - is_active + - other_names + - group_name + - linked_user_id + - created_at + - creator_id + - is_locked + - notes + - urls + properties: + id: + type: number + name: + type: string + updated_at: + type: string + format: date-time + is_active: + type: boolean + other_names: + type: array + items: + type: string + group_name: + type: string + linked_user_id: + type: ["null", "number"] + created_at: + type: string + format: date-time + creator_id: + type: number + is_locked: + type: boolean + notes: + type: ["null", "string"] + ArtistURL: + type: object + required: + - id + - artist_id + - url + - normalized_url + - created_at + - updated_at + - is_active + properties: + id: + type: number + artist_id: + type: number + url: + type: string + format: uri + normalized_url: + type: string + format: uri + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + is_active: + type: boolean + ArtistVersion: + type: object + required: + - id + - artist_id + - name + - updater_id + - created_at + - updated_at + - is_active + - other_names + - notes_changed + - urls + properties: + id: + type: number + artist_id: + type: number + name: + type: string + updater_id: + type: number + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + is_active: + type: boolean + other_names: + type: array + items: + type: string + notes_changed: + type: boolean + urls: + type: array + items: + type: string + format: uri + AvoidPosting: + type: object + required: + - id + - creator_id + - updater_id + - artist_id + - details + - is_active + - created_at + - updated_at + properties: + id: + type: number + creator_id: + type: number + updater_id: + type: number + artist_id: + type: number + staff_notes: + type: string + description: Only visible to Janitor+ + details: + type: string + is_active: + type: boolean + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + AvoidPostingVersion: + type: object + required: + - id + - updater_id + - avoid_posting_id + - details + - is_active + - updated_at + properties: + id: + type: number + updater_id: + type: number + avoid_posting_id: + type: number + details: + type: string + staff_notes: + type: string + description: Only visible to Janitor+ + is_active: + type: boolean + updated_at: + type: string + format: date-time + Ban: + type: object + required: + - id + - user_id + - reason + - expires_at + - banner_id + - created_at + - updated_at + properties: + id: + type: number + user_id: + type: number + reason: + type: string + expires_at: + type: ["string", "null"] + format: date-time + banner_id: + type: number + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + Blip: + type: object + required: + - id + - creator_id + - body + - response_to + - created_at + - updated_at + - is_hidden + - warning_type + - warning_user_id + - updater_id + - creator_name + properties: + id: + type: number + creator_id: + type: number + body: + type: string + response_to: + type: number + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + is_hidden: + type: boolean + warning_type: + type: string + enum: *warning-types + warning_user_id: + type: number + updater_id: + type: number + creator_name: + type: string + BulkRelatedTag: + allOf: + - $ref: "#/components/schemas/RelatedTag" + - type: object + required: + - count + properties: + count: + type: number + BulkUpdateRequest: + type: object + required: + - id + - creator_id + - forum_topic_id + - script + - status + - created_at + - updated_at + - approver_id + - forum_post_id + - title + properties: + id: + type: number + creator_id: + type: number + forum_topic_id: + type: ["number", "null"] + script: + type: string + examples: + - "alias a -> b" + status: + type: string + enum: + - approved + - rejected + - pending + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + approver_id: + type: ["number", "null"] + forum_post_id: + type: ["number", "null"] + title: + type: string + Comment: + type: object + required: + - id + - created_at + - post_id + - creator_id + - body + - score + - updated_at + - updater_id + - do_not_bump_post + - is_hidden + - is_sticky + - warning_type + - warning_user_id + - creator_name + - updater_name + properties: + id: + type: number + created_at: + type: string + format: date-time + post_id: + type: number + creator_id: + type: number + body: + type: string + score: + type: number + updated_at: + type: string + format: date-time + updater_id: + type: number + do_not_bump_post: + type: boolean + deprecated: true + is_hidden: + type: boolean + is_sticky: + type: boolean + warning_type: + type: ["string", "null"] + enum: *warning-types + warning_user_id: + type: ["number", "null"] + creator_name: + type: string + updater_name: + type: string + CurrentUser: + allOf: + - $ref: "#/components/schemas/User" + - type: object + required: + - blacklist_users + - description_collapsed_initially + - hide_comments + - show_hidden_comments + - show_post_statistics + - receive_email_notifications + - enable_keyboard_navigation + - enable_privacy_mode + - style_usernames + - enable_auto_complete + - disabled_cropped_thumbnails + - enable_safe_mode + - disable_responsive_mode + - no_flagging + - disable_user_dmails + - enable_compact_uploader + - replacements_beta + - updated_at + - email + - last_logged_in_at + - last_forum_read_at + - recent_tags + - comment_threshold + - default_image_size + - favorite_tags + - blacklisted_tags + - time_zone + - per_page + - custom_style + - favorite_count + - api_regen_multiplier + - api_burst_limit + - remaining_api_limit + - statement_timeout + - favorite_limit + - tag_query_limit + - has_mail + properties: + blacklist_users: + type: boolean + description_collapsed_initially: + type: boolean + hide_comments: + type: boolean + show_hidden_comments: + type: boolean + show_post_statistics: + type: boolean + receive_email_notifications: + type: boolean + enable_keyboard_navigation: + type: boolean + enable_privacy_mode: + type: boolean + style_usernames: + type: boolean + enable_auto_complete: + type: boolean + can_approve_posts: + type: boolean + disabled_cropped_thumbnails: + type: boolean + enable_safe_mode: + type: boolean + disable_responsive_mode: + type: boolean + no_flagging: + type: boolean + disable_user_dmails: + type: boolean + enable_compact_uploader: + type: boolean + replacements_beta: + type: boolean + updated_at: + type: string + format: date-time + email: + type: string + last_logged_in_at: + type: string + format: date-time + last_forum_read_at: + type: string + format: date-time + recent_tags: + type: string + comment_threshold: + type: number + default_image_sizedefault_image_size: + type: string + enum: + - large + - fit + - fitv + - original + favorite_tags: + type: string + blacklisted_tags: + type: string + time_zone: + type: string + per_page: + type: number + custom_style: + type: string + favorite_count: + type: number + api_regen_multiplier: + type: number + api_burst_limit: + type: number + remaining_api_limit: + type: number + statement_timeout: + type: number + favorite_limit: + type: number + tag_query_limit: + type: number + has_mail: + type: boolean + DMail: + type: object + required: + - id + - owner_id + - from_id + - to_id + - title + - body + - is_read + - is_deleted + - created_at + - updated_at + properties: + id: + type: number + owner_id: + type: number + from_id: + type: number + to_id: + type: number + title: + type: string + body: + type: string + is_read: + type: boolean + is_deleted: + type: boolean + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + DeferredPost: + type: object + required: + - id + - flags + - tags + - rating + - file_ext + - width + - height + - size + - created_at + - uploader + - uploader_id + - score + - fav_count + - is_favorited + - pools + - md5 + - preview_url + - large_url + - file_url + - preview_width + - preview_height + properties: + id: + type: number + flags: + type: string + tags: + type: string + rating: + type: string + enum: *ratings + file_ext: + type: string + width: + type: number + height: + type: number + size: + type: number + created_at: + type: string + format: date-time + uploader: + type: string + uploader_id: + type: number + score: + type: number + fav_count: + type: number + is_favorited: + type: boolean + pools: + type: array + items: + type: number + md5: + type: string + preview_url: + type: ["string", "null"] + large_url: + type: ["string", "null"] + file_url: + type: ["string", "null"] + preview_width: + type: number + preview_height: + type: number + DTextResponse: + type: object + required: + - html + - posts + properties: + html: + type: string + posts: + type: object + patternProperties: + "^0-9+$": + $ref: "#/components/schemas/DeferredPost" + EmailBlacklist: + type: object + required: + - id + - created_at + - updated_at + - domain + - creator_id + - reason + properties: + id: + type: number + created_at: + type: string + updated_at: + type: string + domain: + type: string + creator_id: + type: string + reason: + type: string + ForumPost: + type: object + required: + - id + - topic_id + - creator_id + - updater_id + - body + - is_hidden + - created_at + - updated_at + - warning_type + - warning_user_id + properties: + id: + type: number + topic: + type: number + creator_id: + type: number + updater_id: + type: number + body: + type: string + is_hidden: + type: boolean + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + warning_type: + type: ["string", "null"] + enum: *warning-types + warning_user_id: + type: ["number", "null"] + ForumPostVote: + type: object + required: + - id + - forum_post_id + - creator_id + - score + - created_at + - updated_at + - creator_name + properties: + id: + type: number + forum_post_id: + type: number + creator_id: + type: number + score: + type: number + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + creator_name: + type: string + ForumTopic: + type: object + required: + - id + - creator_id + - updater_id + - title + - response_count + - is_sticky + - is_locked + - is_hidden + - created_at + - updated_at + - category_id + properties: + id: + type: number + creator_id: + type: number + updater_id: + type: number + title: + type: string + response_count: + type: number + is_sticky: + type: boolean + is_locked: + type: boolean + is_hidden: + type: boolean + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + category_id: + type: number + FullCurrentUser: + type: object + allOf: + - $ref: "#/components/schemas/CurrentUser" + - $ref: "#/components/schemas/FullUser" + FullUser: + allOf: + - $ref: "#/components/schemas/User" + - type: object + required: + - wiki_page_version_count + - artist_version_count + - pool_version_count + - forum_post_count + - comment_count + - flag_count + - favorite_count + - positive_feedback_count + - neutral_feedback_count + - negative_feedback_count + - upload_limit + - profile_about + - profile_artinfo + properties: + wiki_page_version_count: + type: number + artist_version_count: + type: number + pool_version_count: + type: number + forum_post_count: + type: number + comment_count: + type: number + flag_count: + type: number + favorite_count: + type: number + positive_feedback_count: + type: number + neutral_feedback_count: + type: number + negative_feedback_count: + type: number + upload_limit: + type: number + profile_about: + type: string + profile_artinfo: + type: string + Help: + type: object + required: + - id + - name + - title + - wiki_page + - related + - created_at + - updated_at + properties: + id: + type: number + name: + type: string + title: + type: string + wiki_page: + type: string + related: + type: string + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + IPBan: + type: object + description: Due to a global filter, the ip_addr is not present no matter your user level. + required: + - id + - creator_id + - reason + - created_at + - updated_at + properties: + id: + type: number + creator_id: + type: number + reason: + type: string + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + IQDBPost: + type: object + required: + - id + - created_at + - updated_at + - up_score + - down_score + - score + - source + - md5 + - rating + - is_note_locked + - is_rating_locked + - is_status_locked + - is_pending + - is_flagged + - is_deleted + - uploader_id + - approver_id + - last_noted_at + - last_comment_bumped_at + - fav_count + - tag_string + - tag_count + - tag_count_general + - tag_count_artist + - tag_count_character + - tag_count_copyright + - file_ext + - file_size + - image_width + - image_height + - parent_id + - has_children + - last_commented_at + - has_active_children + - bit_flags + - tag_count_meta + - locked_tags + - tag_count_species + - tag_count_invalid + - description + - comment_count + - change_seq + - tag_count_lore + - bg_color + - generated_samples + - duration + - is_comment_disabled + - is_comment_locked + - has_large + - has_visible_children + - children_ids + - pool_ids + - is_favorited + properties: + id: + type: number + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + up_score: + type: number + down_score: + type: number + score: + type: number + source: + type: string + md5: + type: string + rating: + type: string + enum: *ratings + is_note_locked: + type: boolean + is_rating_locked: + type: boolean + is_status_locked: + type: boolean + is_pending: + type: boolean + is_flagged: + type: boolean + is_deleted: + type: boolean + uploader_id: + type: number + approver_id: + type: number + last_noted_at: + type: ["null", "string"] + format: date-time + last_comment_bumped_at: + type: ["null", "string"] + format: date-time + fav_count: + type: number + tag_string: + type: string + tag_count: + type: number + tag_count_general: + type: number + tag_count_artist: + type: number + tag_count_character: + type: number + tag_count_copyright: + type: number + file_ext: + type: string + file_size: + type: number + image_width: + type: number + image_height: + type: number + parent_id: + type: ["null", "number"] + has_children: + type: boolean + last_commented_at: + type: ["null", "string"] + format: date-time + has_active_children: + type: boolean + bit_flags: + type: number + tag_count_meta: + type: number + locked_tags: + type: ["null", "string"] + tag_count_species: + type: number + tag_count_invalid: + type: number + description: + type: string + comment_count: + type: number + change_seq: + type: number + tag_count_lore: + type: number + bg_color: + type: ["null", "string"] + generated_samples: + type: ["null", "array"] + items: + type: string + enum: + - 720p + - 480p + - original + duration: + type: ["null", "string"] + is_comment_disabled: + type: boolean + is_comment_locked: + type: boolean + has_large: + type: boolean + has_visible_children: + type: boolean + children_ids: + type: ["null", "string"] + pool_ids: + type: array + items: + type: number + is_favorited: + type: boolean + file_url: + type: string + large_file_url: + type: string + preview_file_url: + type: string + IQDBResponse: + type: object + required: + - hash + - post_id + - score + - post + properties: + hash: + type: string + post_id: + type: number + score: + type: number + post: + type: object + required: + - posts + properties: + posts: + $ref: "#/components/schemas/IQDBPost" + Mascot: + type: object + required: + - id + - creator_id + - display_name + - md5 + - file_ext + - background_color + - artist_url + - artist_name + - active + - created_at + - updated_at + - available_on + - url_path + properties: + id: + type: number + creator_id: + type: number + display_name: + type: string + md5: + type: string + file_ext: + type: string + background_color: + type: string + artist_url: + type: string + format: uri + artist_name: + type: string + active: + type: boolean + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + available_on: + type: array + items: + type: string + url_path: + type: string + format: uri + ModAction: + type: object + required: + - id + - creator_id + - created_at + - updated_at + - action + properties: + id: + type: number + creator_id: + type: number + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + action: + type: string + enum: *modaction-actions + NewsUpdate: + type: object + required: + - id + - message + - creator_id + - updater_id + - created_at + - updated_at + properties: + id: + type: number + message: + type: string + creator_id: + type: number + updater_id: + type: number + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + Note: + type: object + required: + - id + - created_at + - updated_at + - creator_id + - x + - y + - width + - height + - version + - is_active + - post_id + - body + - creator_name + properties: + id: + type: number + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + creator_id: + type: number + x: + type: number + y: + type: number + width: + type: number + height: + type: number + version: + type: number + is_active: + type: boolean + post_id: + type: number + body: + type: string + creator_name: + type: string + NoteVersion: + type: object + required: + - id + - created_at + - updated_at + - x + - y + - width + - height + - body + - version + - is_active + - note_id + - post_id + - updater_id + properties: + id: + type: number + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + x: + type: number + y: + type: number + width: + type: number + height: + type: number + body: + type: string + version: + type: number + is_active: + type: boolean + note_id: + type: number + post_id: + type: number + updater_id: + type: number + Pool: + type: object + required: + - id + - name + - created_at + - updated_at + - creator_id + - description + - is_active + - category + - post_ids + - creator_name + - post_count + properties: + id: + type: number + name: + type: string + updated_at: + type: string + format: date-time + creator_id: + type: number + description: + type: string + is_active: + type: boolean + category: + type: string + enum: *pool-categories + post_ids: + type: array + items: + type: number + created_at: + type: string + format: date-time + creator_name: + type: string + post_count: + type: number + PoolVersion: + type: object + required: + - id + - pool_id + - post_ids + - updater_id + - created_at + - updated_at + - name + - name_changed + - description + - description_changed + - is_active + - is_locked + - category + - version + - added_post_ids + - removed_post_ids + properties: + id: + type: number + pool_id: + type: number + post_ids: + type: array + items: + type: number + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + updater_id: + type: number + name: + type: string + name_changed: + type: boolean + description: + type: string + description_changed: + type: boolean + is_active: + type: boolean + is_locked: + type: boolean + category: + type: string + enum: *pool-categories + version: + type: number + added_post_ids: + type: array + items: + type: number + removed_post_ids: + type: array + items: + type: number + Post: + type: object + required: + - id + - created_at + - updated_at + - file + - preview + - sample + - score + - tags + - locked_tags + - change_seq + - flags + - rating + - fav_count + - sources + - pools + - relationships + - approver_id + - uploader_id + - description + - comment_count + - is_favorited + - has_notes + - duration + properties: + id: + type: number + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + file: + type: object + required: + - width + - height + - ext + - size + - md5 + - url + properties: + width: + type: number + height: + type: number + ext: + type: string + size: + type: number + md5: + type: string + url: + type: ["string", "null"] + preview: + type: object + required: + - width + - height + - url + properties: + width: + type: number + height: + type: number + url: + type: ["string", "null"] + sample: + type: object + required: + - has + - height + - width + - url + - alternates + properties: + has: + type: boolean + height: + type: ["number", "null"] + width: + type: ["number", "null"] + url: + type: ["string", "null"] + alternates: + type: object + properties: + 480p: + $ref: "#/components/schemas/PostSampleAlternate" + 720p: + $ref: "#/components/schemas/PostSampleAlternate" + original: + $ref: "#/components/schemas/PostSampleAlternate" + score: + type: object + required: + - up + - down + - total + properties: + up: + type: number + down: + type: number + total: + type: number + tags: + type: object + required: + - general + - artist + - copyright + - character + - species + - invalid + - meta + - lore + properties: + general: + type: array + items: + type: string + artist: + type: array + items: + type: string + copyright: + type: array + items: + type: string + character: + type: array + items: + type: string + species: + type: array + items: + type: string + invalid: + type: array + items: + type: string + meta: + type: array + items: + type: string + lore: + type: array + items: + type: string + locked_tags: + type: ["array", "null"] + items: + type: string + change_seq: + type: number + flags: + type: object + required: + - pending + - flagged + - note_locked + - status_locked + - rating_locked + - deleted + properties: + pending: + type: boolean + flagged: + type: boolean + note_locked: + type: boolean + status_locked: + type: boolean + rating_locked: + type: boolean + deleted: + type: boolean + rating: + type: string + enum: *ratings + fav_count: + type: number + sources: + type: array + items: + type: string + pools: + type: array + items: + type: number + relationships: + type: object + required: + - parent_id + - has_children + - has_active_children + - children + properties: + parent_id: + type: ["number", "null"] + has_children: + type: boolean + has_active_children: + type: boolean + children: + type: array + items: + type: number + approver_id: + type: ["number", "null"] + uploader_id: + type: number + description: + type: string + comment_count: + type: number + is_favorited: + type: boolean + has_notes: + type: boolean + duration: + type: ["number", "null"] + PostApproval: + type: object + required: + - id + - user_id + - post_id + - created_at + - updated_at + properties: + id: + type: number + user_id: + type: number + post_id: + type: number + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + PostDisapproval: + type: object + required: + - id + - user_id + - post_id + - reason + - message + - created_at + - updated_at + properties: + id: + type: number + user_id: + type: number + post_id: + type: number + reason: + type: string + enum: + - borderline_quality + - borderline_relevancy + - other + message: + type: ["string", "null"] + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + PostEvent: + type: object + required: + - id + - creator_id + - post_id + - action + - created_at + properties: + id: + type: number + creator_id: + type: ["number", "null"] + post_id: + type: number + action: + type: string + enum: *post-event-actions + created_at: + type: string + format: date-time + PostFlag: + type: object + required: + - id + - created_at + - post_id + - reason + - creator_id + - is_resolved + - updated_at + - is_deletion + - type + properties: + id: + type: number + created_at: + type: string + format: date-time + post_id: + type: number + reason: + type: string + creator_id: + type: ["number", "null"] + is_resolved: + type: boolean + updated_at: + type: string + format: date-time + is_deletion: + type: boolean + type: + type: string + enum: + - flag + - deletion + PostReplacement: + type: object + required: + - id + - created_at + - updated_at + - post_id + - creator_id + - approver_id + - file_ext + - file_size + - image_height + - image_width + - md5 + - source + - file_name + - status + - reason + properties: + id: + type: number + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + post_id: + type: number + creator_id: + type: number + approver_id: + type: ["number", "null"] + file_ext: + type: string + file_size: + type: number + image_height: + type: number + image_width: + type: number + md5: + type: string + source: + type: string + file_name: + type: string + status: + type: string + enum: + - prompted + - approved + - rejected + - pending + reason: + type: string + PostSampleAlternate: + type: object + required: + - type + - height + - width + - urls + properties: + type: + type: string + enum: + - video + height: + type: number + width: + type: number + urls: + type: array + items: + anyOf: + - type: ["string", "null"] + format: "uri" + - type: ["string", "null"] + format: "uri" + PostSet: + type: object + required: + - id + - created_at + - updated_at + - creator_id + - is_public + - name + - shortname + - description + - transfer_on_delete + - post_ids + - post_count + properties: + id: + type: number + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + creator_id: + type: number + is_public: + type: boolean + name: + type: string + shortname: + type: string + description: + type: string + post_count: + type: number + transfer_on_delete: + type: boolean + post_ids: + type: array + items: + type: number + PostVersion: + type: object + required: + - id + - post_id + - tags + - updater_id + - updated_at + - rating + - parent_id + - source + - description + - reason + - locked_tags + - added_tags + - removed_tags + - added_locked_tags + - removed_locked_tags + - rating_changed + - parent_changed + - source_changed + - description_changed + - version + - obsolete_added_tags + - obsolete_removed_tags + - unchanged_tags + - updater_name + properties: + id: + type: number + post_id: + type: number + tags: + type: string + updater_id: + type: number + updated_at: + type: string + format: date-time + rating: + type: string + enum: *ratings + parent_id: + type: ["number", "null"] + source: + type: string + description: + type: string + reason: + type: ["string", "null"] + locked_tags: + type: ["string", "null"] + added_tags: + type: array + items: + type: string + removed_tags: + type: array + items: + type: string + added_locked_tags: + type: array + items: + type: string + removed_locked_tags: + type: array + items: + type: string + rating_changed: + type: boolean + parent_changed: + type: boolean + source_changed: + type: boolean + description_changed: + type: boolean + version: + type: number + obsolete_added_tags: + type: string + obsolete_removed_tags: + type: string + unchanged_tags: + type: string + updater_name: + type: string + RelatedTag: + type: object + required: + - name + - category_id + properties: + name: + type: string + category_id: + type: number + enum: *tag-categories + Tag: + type: object + required: + - id + - name + - post_count + - related_tags + - related_tags_updated_at + - category + - is_locked + - created_at + - updated_at + properties: + id: + type: number + name: + type: string + post_count: + type: number + related_tags: + type: array + items: + type: string + related_tags_updated_at: + type: ["string", "null"] + format: date-time + category: + type: number + enum: *tag-categories + is_locked: + type: boolean + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + TagAlias: + type: object + required: + - id + - antecedent_name + - reason + - creator_id + - created_at + - forum_post_id + - updated_at + - forum_topic_id + - consequent_name + - status + - post_count + - approver_id + properties: + id: + type: number + antecedent_name: + type: string + reason: + type: string + creator_id: + type: number + created_at: + type: ["string", "null"] + format: date-time + forum_post_id: + type: ["number", "null"] + updated_at: + type: ["string", "null"] + format: date-time + forum_topic_id: + type: ["number", "null"] + consequent_name: + type: string + status: + type: string + description: | + Note: The "error" status will be proceeded by an error, ex: "error: Validation failed: A tag alias for tag_name already exists" + enum: *tag-request-statuses + post_count: + type: number + approver_id: + type: ["number", "null"] + TagImplication: + type: object + required: + - id + - reason + - creator_id + - created_at + - forum_post_id + - antecedent_name + - consequent_name + - status + - forum_topic_id + - updated_at + - descendant_names + - approver_id + properties: + id: + type: number + reason: + type: string + creator_id: + type: number + created_at: + type: string + format: date-time + forum_post_id: + type: ["number", "null"] + antecedent_name: + type: string + consequent_name: + type: string + status: + type: string + description: | + Note: The "error" status will be proceeded by an error, ex: "error: Validation failed: A tag alias for tag_name already exists" + enum: *tag-request-statuses + forum_topic_id: + type: ["number", "null"] + updated_at: + type: string + format: date-time + descendant_names: + type: array + items: + type: string + approver_id: + type: ["number", "null"] + TagPreview: + type: object + required: + - a + - type + - tagTypeA + properties: + a: + type: string + description: The name if type=tag, else the antecedent. + b: + type: string + description: The consequent, only present if type=alias or type=implication. + type: + type: string + enum: + - tag + - implication + - alias + tagTypeA: + type: ["number", "null"] + enum: *tag-categories + tagTypeB: + type: ["number", "null"] + enum: *tag-categories + TagTypeVersion: + type: object + required: + - id + - created_at + - updated_at + - old_type + - new_type + - is_locked + - tag_id + - creator_id + properties: + id: + type: number + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + old_type: + type: number + enum: *tag-categories + new_type: + type: number + enum: *tag-categories + is_locked: + type: boolean + tag_id: + type: number + creator_id: + type: number + Takedown: + type: object + required: + - id + - status + - approver_id + - reason_hidden + - created_at + - updated_at + - post_count + properties: + id: + type: number + status: + type: string + enum: + - approved + - denied + - partial + - pending + approver_id: + type: ["number", "null"] + reason_hidden: + type: boolean + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + post_count: + type: number + Ticket: + type: object + required: + - id + - creator_id + - reason + - disp_id + - qtype + - status + - created_at + - updated_at + - response + - handler_id + - report_reason + - accused_id + properties: + id: + type: number + creator_id: + type: number + reason: + type: string + disp_id: + type: number + qtype: + type: string + enum: *ticket-types + status: + type: string + enum: *ticket-statuses + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + response: + type: string + handler_id: + type: ["number", "null"] + claimant_id: + type: ["number", "null"] + description: Only visible to Moderator+. + report_reason: + type: ["string", "null"] + accused_id: + type: ["number", "null"] + UploadWhitelist: + type: object + required: + - id + - created_at + - updated_at + - pattern + - note + - hidden + - allowed + - reason + properties: + id: + type: number + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + pattern: + type: string + note: + type: string + hidden: + type: boolean + allowed: + type: boolean + reason: + type: string + Upload: + type: object + required: + - id + - source + - rating + - uploader_id + - tag_string + - status + - backtrace + - post_id + - md5_confirmation + - created_at + - updated_at + - parent_id + - md5 + - file_ext + - file_size + - image_width + - image_height + - description + - uploader_name + properties: + id: + type: number + source: + type: string + rating: + type: string + enum: *ratings + uploader_id: + type: number + tag_string: + type: string + status: + type: string + description: | + Note: The "error" status will be proceeded by an error, ex: "error: RuntimeError - No file or source URL provided" + enum: + - completed + - duplicate + - error + - processing + - pending + backtrace: + type: ["string", "null"] + post_id: + type: ["number", "null"] + md5_confirmation: + type: "null" + deprecated: true + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + parent_id: + type: ["null", "number"] + md5: + type: ["string", "null"] + file_ext: + type: ["string", "number"] + file_size: + type: ["null", "number"] + image_width: + type: ["null", "number"] + image_height: + type: ["null", "number"] + description: + type: string + uploader_name: + type: string + User: + type: object + required: + - id + - created_at + - name + - level + - base_upload_limit + - post_upload_count + - post_update_count + - note_update_count + - is_banned + - can_approve_posts + - can_upload_free + - level_string + - avatar_id + properties: + id: + type: number + created_at: + type: string + format: date-time + name: + type: string + level: + type: number + base_upload_limit: + type: number + post_upload_count: + type: number + post_update_count: + type: number + note_update_count: + type: number + is_banned: + type: boolean + can_approve_posts: + type: boolean + can_upload_free: + type: boolean + level_string: + type: string + avatar_id: + type: ["number", "null"] + UserFeedback: + type: object + required: + - id + - user_id + - creator_id + - category + - body + - created_at + - updated_at + - updater_id + - is_deleted + properties: + id: + type: number + user_id: + type: number + creator_id: + type: number + category: + type: string + enum: *feedback-categories + body: + type: string + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + updater_id: + type: number + is_deleted: + type: boolean + UserNameChangeRequest: + type: object + required: + - id + - approver_id + - user_id + - original_name + - desired_name + - created_at + - updated_at + - status + properties: + id: + type: number + approver_id: + type: number + user_id: + type: number + original_name: + type: string + desired_name: + type: string + change_reason: + type: string + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + status: + type: string + enum: + - approved + WikiPage: + type: object + required: + - id + - created_at + - updated_at + - title + - body + - creator_id + - is_locked + - updater_id + - is_deleted + - other_names + - parent + - creator_name + - category_id + properties: + id: + type: number + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + title: + type: string + body: + type: string + creator_id: + type: number + is_locked: + type: boolean + updater_id: + type: number + is_deleted: + type: boolean + other_names: + type: array + items: + type: string + parent: + type: ["string", "null"] + creator_name: + type: string + category_id: + type: number + enum: *tag-categories + WikiPageVersion: + type: object + required: + - id + - created_at + - updated_at + - title + - body + - updater_id + - wiki_page_id + - is_locked + - other_names + - is_deleted + - reason + - parent + properties: + id: + type: number + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + title: + type: string + body: + type: string + updater_id: + type: number + wiki_page_id: + type: number + is_locked: + type: boolean + other_names: + type: array + items: + type: string + is_deleted: + type: boolean + reason: + type: ["string", "null"] + parent: + type: ["string", "null"] + responses: + NotFound: + description: Not Found + content: + application/json: + schema: + type: object + required: + - success + - reason + properties: + success: + type: boolean + enum: + - false + reason: + type: string + enum: + - "not found" + AccessDenied: + description: Access Denied + content: + application/json: + schema: + type: object + required: + - success + - reason + properties: + success: + type: boolean + enum: + - false + reason: + type: string + enum: + - "Access Denied" + ExpectedError: + description: Invalid Input Data + content: + application/json: + schema: + type: object + required: + - errors + examples: + - { "errors": { "key": [ "the error" ] } } + properties: + errors: + type: object + patternProperties: + "^[a-z]+$": + type: array + items: + type: string + MessageError: + description: Error + content: + application/json: + schema: + type: object + required: + - success + - message + properties: + success: + type: boolean + enum: + - false + message: + type: string + code: + type: ["string", "null"] + requestBodies: + warning: + content: + application/json: + schema: + type: object + required: + - record_type + properties: + record_type: + type: string + enum: + - unmark + - ban + - record + - warning + +paths: + # Artists + /artists.json: + get: + summary: Search Artists + operationId: searchArtists + tags: + - Artists + parameters: + - $ref: "#/components/parameters/limit" + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/id" + - name: search[order] + in: query + schema: + type: string + enum: + - id_asc + - id_desc + - updated_at + - name + - post_count + - name: search[name] + in: query + schema: + type: string + - name: search[group_name] + in: query + schema: + type: string + - name: search[any_other_name_like] + in: query + schema: + type: string + - name: search[any_name_matches] + in: query + schema: + type: string + - name: search[any_name_or_url_matches] + in: query + schema: + type: string + - name: search[url_matches] + in: query + schema: + type: string + - name: search[creator_name] + in: query + schema: + type: string + - name: search[creator_id] + in: query + schema: + type: string + - name: search[has_tag] + in: query + schema: + type: string + - name: search[is_linked] + in: query + schema: + type: string + responses: + 200: + description: Success + content: + application/json: + schema: + type: array + items: + allOf: + - $ref: "#/components/schemas/Artist" + - type: object + required: + - domains + - urls + properties: + domains: + type: array + example: [["e621.net", 1]] + items: + type: array + items: + anyOf: + - type: string + - type: number + urls: + type: array + items: + $ref: "#/components/schemas/ArtistURL" + post: + summary: Create Artist + operationId: createArtist + tags: + - Artists + security: + - basicAuth: [] + description: | + `other_names` & `urls` are silently truncated to 25 entries. + `notes` is silently truncated to the wiki page limit (250,000). + Individual `other_names` are silently truncated to 100 characters. + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + required: + - artist[name] + properties: + artist[name]: + type: string + artist[other_names]: + type: array + items: + type: string + artist[other_names_string]: + type: string + artist[url_string]: + type: string + artist[notes]: + type: string + artist[group_name]: + type: string + artist[linked_user_id]: + type: ["number", "null"] + description: Only usable for Janitor+ + artist[is_locked]: + type: boolean + description: Only usable for Janitor+ + responses: + 201: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/Artist" + 403: + $ref: "#/components/responses/AccessDenied" + 422: + $ref: "#/components/responses/ExpectedError" + /artists/{idOrName}.json: + get: + summary: Get Artist + operationId: getArtist + tags: + - Artists + parameters: + - name: idOrName + in: path + required: true + description: The ID or name of the artist to get. + schema: + type: ["number", "string"] + responses: + 200: + description: Success + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/Artist" + - type: object + required: + - domains + - urls + properties: + domains: + type: array + items: + type: array + example: [["e621.net", 1]] + items: + anyOf: + - type: string + - type: number + urls: + type: array + items: + $ref: "#/components/schemas/ArtistURL" + 404: + $ref: "#/components/responses/NotFound" + patch: + summary: Edit Artist + operationId: editArtist + tags: + - Artists + security: + - basicAuth: [] + description: | + If an artist is locked, you must be Janitor+ to edit them. + `other_names` & `urls` are silently truncated to 25 entries. + `notes` is silently truncated to the wiki page limit (250,000). + Individual `other_names` are silently truncated to 100 characters. + If an artist is on the avoid posting list, you must have the bd staff user flag to edit name, other_names, or group_name. + parameters: + - name: idOrName + in: path + required: true + description: The ID or name of the artist to edit. + schema: + type: ["number", "string"] + requestBody: + content: + application/c-www-form-urlencoded: + schema: + type: object + properties: + artist[name]: + type: string + description: Only usable for Janitor+ + artist[other_names]: + type: array + items: + type: string + artist[other_names_string]: + type: string + artist[url_string]: + type: string + artist[notes]: + type: string + artist[group_name]: + type: string + artist[linked_user_id]: + type: ["number", "null"] + description: Only usable for Janitor+ + artist[is_locked]: + type: boolean + description: Only usable for Janitor+ + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + delete: + summary: Delete Artist + operationId: deleteArtist + tags: + - Artists + security: + - basicAuth: [] + description: | + You must be an Admin+ to delete an artist. + parameters: + - name: idOrName + in: path + required: true + description: The ID or name of the artist to edit. + schema: + type: ["number", "string"] + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + /artists/{idOrName}/revert.json: + put: + summary: Revert Artist + operationId: revertArtist + tags: + - Artists + security: + - basicAuth: [] + description: | + If an artist is locked, you must be Janitor+ to revert them. + parameters: + - name: idOrName + in: path + required: true + description: The ID or name of the artist to revert. + schema: + type: ["number", "string"] + - name: version_id + in: query + required: true + description: The version ID to revert to. + schema: + type: number + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + # Artist Versions + /artist_versions.json: + get: + summary: Search Artist Versions + operationId: searchArtistVersions + tags: + - Artist Versions + description: When no results are found, an object with an `artist_versions` key is returned. + parameters: + - $ref: "#/components/parameters/limit" + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/id" + - $ref: "#/components/parameters/ip_addr" + - name: search[order] + in: query + description: The order of the results. + schema: + type: string + enum: + - id_asc + - id_desc + - name + - name: search[name] + in: query + description: The name of the artist. + schema: + type: string + - name: search[artist_id] + in: query + description: The id of the artist. + schema: + type: string + - name: search[updater_name] + in: query + description: The name of the user that updated the artist. + schema: + type: string + - name: search[updater_id] + in: query + description: The id of the user that updated the artist. + schema: + type: string + responses: + 200: + description: Success + content: + application/json: + schema: + anyOf: + - type: array + items: + $ref: "#/components/schemas/ArtistVersion" + - type: object + description: No Results + required: + - artist_versions + properties: + artist_versions: + type: array + maxItems: 0 + # Artist URLs + /artist_urls.json: + get: + summary: Search Artist URLs + operationId: searchArtistUrls + tags: + - Artist URLs + parameters: + - $ref: "#/components/parameters/limit" + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/id" + - name: search[order] + in: query + description: The order of the results. + schema: + type: string + enum: + - id + - id_asc + - id_desc + - artist_id + - artist_id_asc + - artist_id_desc + - url + - url_asc + - url_desc + - normalized_url + - normalized_url_asc + - normalized_url_desc + - created_at + - created_at_asc + - created_at_desc + - updated_at + - updated_at_asc + - updated_at_desc + - name: search[artist_name] + in: query + description: The name of the artist. + schema: + type: string + - name: search[artist_id] + in: query + description: The id of the artist. + schema: + type: string + - name: search[is_active] + in: query + description: If the artist url is active. + schema: + type: boolean + - name: search[url] + in: query + description: The url. + schema: + type: string + - name: search[normalized_url] + in: query + description: The normalized url. (http:, trailing `/`) + schema: + type: string + - name: search[artist] + in: query + description: Legacy nested search for artist. Supports the same parameters as /artists.json. + deprecated: true + schema: + type: object + - name: search[url_matches] + in: query + description: Legacy name for `search[url]`. + deprecated: true + schema: + type: string + - name: search[normalized_url_matches] + in: query + description: Legacy name for `search[normalized_url]`. + deprecated: true + schema: + type: string + responses: + 200: + description: Success + content: + application/json: + schema: + type: array + items: + allOf: + - $ref: "#/components/schemas/ArtistURL" + - type: object + required: + - artist + properties: + artist: + $ref: "#/components/schemas/Artist" + # Avoid Posting Entries + /avoid_postings.json: + get: + summary: Search Avoid Posting Entries + operationId: searchAvoidPostings + tags: + - Avoid Posting Entries + description: When no results are found, an object with an `avoid_postings` key is returned. + parameters: + - $ref: "#/components/parameters/limit" + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/id" + - $ref: "#/components/parameters/ip_addr" + - name: search[order] + in: query + schema: + type: string + enum: + - id_asc + - id_desc + - name: search[creator_name] + in: query + schema: + type: string + - name: search[creator_id] + in: query + schema: + type: string + - name: search[any_name_matches] + in: query + description: Any name matching. + schema: + type: string + - name: search[artist_name] + in: query + description: The artist name of the avoid posting entry. + schema: + type: string + - name: search[artist_id] + in: query + description: The artist id for the avoid posting entry. + schema: + type: string + - name: search[any_other_name_matches] + in: query + description: Any other name matching. + schema: + type: string + - name: search[details] + in: query + description: The details of the avoid posting entry. + schema: + type: string + - name: search[staff_notes] + in: query + description: The staff notes on the avoid posting entry. Must be Janitor+ to use. + schema: + type: string + - name: search[is_active] + in: query + description: If the avoid posting entry is active. + schema: + type: boolean + responses: + 200: + description: Success + content: + application/json: + schema: + anyOf: + - type: array + items: + $ref: "#/components/schemas/AvoidPosting" + - type: object + description: No Results + required: + - avoid_postings + properties: + avoid_postings: + type: array + maxItems: 0 + post: + summary: Create Avoid Posting Entry + operationId: createAvoidPosting + tags: + - Avoid Posting Entries + security: + - basicAuth: [] + description: Must have the bd staff user flag. When no results are found, an object with an `avoid_posting_versions` key is returned. + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + avoid_posting[details]: + type: string + avoid_posting[staff_notes]: + type: string + avoid_posting[is_active]: + type: boolean + avoid_posting[artist_attributes][id]: + type: number + avoid_posting[artist_attributes][name]: + type: string + description: If provided and the artist does not exist, an artist will be created. + avoid_posting[artist_attributes][other_names_string]: + type: string + avoid_posting[artist_attributes][other_names]: + type: array + items: + type: string + avoid_posting[artist_attributes][group_name]: + type: string + avoid_posting[artist_attributes][linked_user_id]: + type: number + responses: + 201: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/AvoidPosting" + 403: + $ref: "#/components/responses/AccessDenied" + 422: + $ref: "#/components/responses/ExpectedError" + /avoid_postings/{idOrArtistName}.json: + get: + summary: Get Avoid Posting Entry + operationId: getAvoidPosting + tags: + - Avoid Posting Entries + parameters: + - name: idOrArtistName + in: path + required: true + description: The ID of the avoid posting entry, or the name of the artist. + schema: + type: string + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/AvoidPosting" + 404: + $ref: "#/components/responses/NotFound" + patch: + summary: Edit Avoid Posting Entry + operationId: editAvoidPosting + tags: + - Avoid Posting Entries + security: + - basicAuth: [] + description: Must have the bd staff user flag. + parameters: + - name: idOrArtistName + in: path + required: true + description: The ID of the avoid posting entry, or the name of the artist. + schema: + type: string + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + avoid_posting[details]: + type: string + avoid_posting[staff_notes]: + type: string + avoid_posting[is_active]: + type: boolean + avoid_posting[artist_attributes][name]: + type: string + avoid_posting[artist_attributes][other_names_string]: + type: string + avoid_posting[artist_attributes][other_names]: + type: array + items: + type: string + avoid_posting[artist_attributes][group_name]: + type: string + avoid_posting[artist_attributes][linked_user_id]: + type: number + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/AvoidPosting" + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + delete: + summary: Destroy Avoid Posting Entry + operationId: destroyAvoidPosting + tags: + - Avoid Posting Entries + security: + - basicAuth: [] + description: Must have the bd staff user flag. + parameters: + - name: idOrArtistName + in: path + required: true + description: The ID of the avoid posting entry, or the name of the artist. + schema: + type: string + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + /avoid_postings/{idOrArtistName}/delete.json: + put: + summary: Delete Avoid Posting Entry + operationId: deleteAvoidPosting + tags: + - Avoid Posting Entries + security: + - basicAuth: [] + description: Must have the bd staff user flag. + parameters: + - name: idOrArtistName + in: path + required: true + description: The ID of the avoid posting entry, or the name of the artist. + schema: + type: string + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + /avoid_postings/{idOrArtistName}/undelete.json: + put: + summary: Undelete Avoid Posting Entry + operationId: undeleteAvoidPosting + tags: + - Avoid Posting Entries + security: + - basicAuth: [] + description: Must have the bd staff user flag. + parameters: + - name: idOrArtistName + in: path + required: true + description: The ID of the avoid posting entry, or the name of the artist. + schema: + type: string + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + # Avoid Posting Versions + /avoid_posting_versions.json: + get: + summary: Search Avoid Posting Versions + operationId: searchAvoidPostingVersions + tags: + - Avoid Posting Versions + parameters: + - $ref: "#/components/parameters/limit" + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/id" + - $ref: "#/components/parameters/ip_addr" + - name: search[order] + in: query + description: The order of the results. + schema: + type: string + enum: + - id_asc + - id_desc + - name: search[updater_name] + in: query + description: The name of the updater of the avoid posting entry. + schema: + type: string + - name: search[updater_id] + in: query + description: The ID of the updater of the avoid posting entry. + schema: + type: string + - name: search[any_name_matches] + in: query + description: Any name matching. + schema: + type: string + - name: search[artist_name] + in: query + description: The artist name of the avoid posting entry. + schema: + type: string + - name: search[artist_id] + in: query + description: The artist id for the avoid posting entry. + schema: + type: string + - name: search[any_other_name_matches] + in: query + description: Any other name matching. + schema: + type: string + - name: search[group_name] + in: query + description: Any other name matching. + schema: + type: string + - name: search[is_active] + in: query + description: If the avoid posting entry is active. + schema: + type: boolean + responses: + 200: + description: Success + content: + application/json: + schema: + anyOf: + - type: array + items: + $ref: "#/components/schemas/AvoidPostingVersion" + - type: object + description: No Results + required: + - avoid_posting_versions + properties: + avoid_posting_versions: + type: array + maxItems: 0 + # Bans + /bans.json: + get: + summary: Search Bans + operationId: searchBans + tags: + - Bans + description: When no results are found, an object with an `bans` key is returned. + parameters: + - $ref: "#/components/parameters/limit" + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/id" + - name: search[order] + in: query + description: The order of the results. + schema: + type: string + enum: + - id_asc + - id_desc + - expires_at_desc + - name: search[banner_id] + in: query + description: The ID of the banner. + schema: + type: string + - name: search[banner_name] + in: query + description: The name of banner. + schema: + type: string + - name: search[user_id] + in: query + description: The ID of the banned user. + schema: + type: string + - name: search[user_name] + in: query + description: The name of the banned user. + schema: + type: string + - name: search[reason_matches] + in: query + description: The reason of the ban. + schema: + type: string + - name: search[expired] + in: query + description: If the ban is expired. + schema: + type: string + responses: + 200: + description: Success + content: + application/json: + schema: + anyOf: + - type: array + items: + $ref: "#/components/schemas/Ban" + - type: object + description: No Results + required: + - bans + properties: + bans: + type: array + maxItems: 0 + /bans/{id}.json: + get: + summary: Get Ban + operationId: getBan + tags: + - Bans + parameters: + - name: id + in: path + description: The ID of the ban to get. + required: true + schema: + type: number + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/Ban" + 404: + $ref: "#/components/responses/NotFound" + # Blips + /blips.json: + get: + summary: Search Blips + operationId: searchBlips + tags: + - Blips + description: When no results are found, an object with an `blips` key is returned. + parameters: + - $ref: "#/components/parameters/limit" + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/id" + - $ref: "#/components/parameters/ip_addr" + - name: search[order] + in: query + schema: + type: string + enum: + - id_asc + - id_desc + - updated_at + - updated_at_desc + - name: search[creator_id] + in: query + schema: + type: number + - name: search[creator_name] + in: query + schema: + type: string + - name: search[body_matches] + in: query + schema: + type: string + - name: search[response_to] + in: query + schema: + type: number + responses: + 200: + description: Success + content: + application/json: + schema: + anyOf: + - type: array + items: + $ref: "#/components/schemas/Blip" + - type: object + description: No Results + required: + - blips + properties: + blips: + type: array + maxItems: 0 + post: + summary: Create Blip + operationId: createBlip + tags: + - Blips + security: + - basicAuth: [] + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + required: + - blip[body] + properties: + blip[body]: + type: string + blip[response_to]: + type: number + responses: + 201: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/Blip" + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + /blips/{id}.json: + get: + summary: Get Blip + operationId: getBlip + tags: + - Blips + parameters: + - name: id + in: path + description: The ID of the blip to get. + required: true + schema: + type: number + responses: + 200: + description: Success + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Blip" + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + patch: + summary: Edit Blip + operationId: editBlip + tags: + - Blips + security: + - basicAuth: [] + description: Unless Admin+, blips cannot be edited after 5 minutes. Marked blips cannot be edited. + parameters: + - name: id + in: path + description: The ID of the blip. + required: true + schema: + type: number + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + blip[body]: + type: string + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/Blip" + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + delete: + summary: Delete Blip + operationId: deleteBlip + tags: + - Blips + security: + - basicAuth: [] + description: | + You must be Admin+. + parameters: + - name: id + in: path + required: true + description: The ID of the blip. + schema: + type: number + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + /blips/{id}/hide.json: + post: + summary: Hide Blip + operationId: hideBlip + tags: + - Blips + security: + - basicAuth: [] + description: | + You must be the creator or Moderator+. + parameters: + - name: id + in: path + required: true + description: The ID of the blip. + schema: + type: number + responses: + 201: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/Blip" + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + /blips/{id}/unhide.json: + post: + summary: Unhide Blip + operationId: unhideBlip + tags: + - Blips + security: + - basicAuth: [] + description: | + You must be Moderator+. + parameters: + - name: id + in: path + required: true + description: The ID of the blip. + schema: + type: number + responses: + 201: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/Blip" + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + /blips/{id}/warning.json: + post: + summary: Mark Blip + operationId: markBlip + tags: + - Blips + security: + - basicAuth: [] + description: You must be Moderator+. + parameters: + - name: id + in: path + required: true + description: The ID of the blip. + schema: + type: number + requestBody: + $ref: "#/components/requestBodies/warning" + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/DTextResponse" + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + # Bulk Update Requests + /bulk_update_requests.json: + get: + summary: Search Bulk Update Requests + operationId: searchBulkUpdateRequests + tags: + - Bulk Update Requests + description: When no results are found, an object with an `bulk_update_requests` key is returned. + parameters: + - $ref: "#/components/parameters/limit" + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/id" + - name: search[order] + in: query + schema: + type: string + enum: + - id_asc + - id_desc + - status_desc + - updated_at_desc + - updated_at_asc + - name: search[user_id] + in: query + schema: + type: number + - name: search[user_name] + in: query + schema: + type: string + - name: search[approver_id] + in: query + schema: + type: number + - name: search[approver_name] + in: query + schema: + type: string + - name: search[forum_topic_id] + in: query + schema: + type: number + - name: search[forum_post_id] + in: query + schema: + type: number + - name: search[status] + in: query + schema: + type: string + description: Multiple can be specified via comma separating. + enum: + - approved + - rejected + - pending + - name: search[title_matches] + in: query + schema: + type: string + - name: search[script_matches] + in: query + schema: + type: string + responses: + 200: + description: Success + content: + application/json: + schema: + anyOf: + - type: array + items: + $ref: "#/components/schemas/BulkUpdateRequest" + - type: object + description: No Results + required: + - bulk_update_requests + properties: + bulk_update_requests: + type: array + maxItems: 0 + post: + summary: Create Bulk Update Request + operationId: createBulkUpdateRequest + tags: + - Bulk Update Requests + security: + - basicAuth: [] + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + required: + - bulk_update_request[script] + - bulk_update_request[title] + - bulk_update_request[reason] + properties: + bulk_update_request[script]: + type: string + bulk_update_request[title]: + type: string + bulk_update_request[reason]: + type: string + bulk_update_request[forum_topic_id]: + type: number + bulk_update_request[skip_forum]: + type: boolean + description: Only usable for Admin+ + responses: + 201: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/BulkUpdateRequest" + 403: + $ref: "#/components/responses/AccessDenied" + 422: + $ref: "#/components/responses/ExpectedError" + /bulk_update_request/{id}.json: + get: + summary: Get Bulk Update Request + operationId: getBulkUpdateRequest + tags: + - Bulk Update Requests + parameters: + - name: id + in: path + required: true + description: The ID of the bulk update request. + schema: + type: number + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/BulkUpdateRequest" + 404: + $ref: "#/components/responses/NotFound" + patch: + summary: Edit Bulk Update Request + operationId: editBulkUpdateRequest + tags: + - Bulk Update Requests + security: + - basicAuth: [] + description: You must be the creator of the BUR, or Admin+. + parameters: + - name: id + in: path + required: true + description: The ID of the bulk update request. + schema: + type: number + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + bulk_update_request[script]: + type: string + bulk_update_request[forum_topic_id]: + description: You must be Admin+. + type: string + bulk_update_request[forum_post_id]: + description: You must be Admin+. + type: string + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + delete: + summary: Reject Bulk Update Request + operationId: rejectBulkUpdateRequest + tags: + - Bulk Update Requests + security: + - basicAuth: [] + description: You must be the creator of the BUR, or Admin+. + parameters: + - name: id + in: path + required: true + description: The ID of the bulk update request. + schema: + type: number + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + /bulk_update_request/{id}/approve.json: + post: + summary: Approve Bulk Update Request + operationId: approveBulkUpdateRequest + tags: + - Bulk Update Requests + description: Must be Admin+. + parameters: + - name: id + in: path + required: true + description: The ID of the bulk update request. + schema: + type: number + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + # Comments + /comments.json: + get: + summary: Search Comments + operationId: searchComments + tags: + - Comments + description: For searching comments, group_by=comment must be set. When no results are found, an object with an `comments` key is returned. + parameters: + - $ref: "#/components/parameters/limit" + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/id" + - $ref: "#/components/parameters/ip_addr" + - name: search[order] + in: query + schema: + type: string + enum: + - id_asc + - id_desc + - status + - status_desc + - updated_at_desc + - in: query + name: group_by + schema: + type: string + enum: + - comment + - post + - in: query + name: search[body_matches] + schema: + type: string + - in: query + name: search[post_id] + description: Accepts a comma separated list. + schema: + type: number + - in: query + name: search[post_tags_match] + schema: + type: string + - in: query + name: search[post_note_updater_name] + schema: + type: string + - in: query + name: search[post_note_updater_id] + schema: + type: number + - in: query + name: search[creator_name] + schema: + type: string + - in: query + name: search[creator_id] + schema: + type: number + - in: query + name: search[is_sticky] + schema: + type: boolean + - in: query + name: search[is_hidden] + description: Only usable by Moderator+ + schema: + type: boolean + - in: query + name: search[do_not_bump_post] + schema: + type: boolean + responses: + 200: + description: Success + content: + application/json: + schema: + anyOf: + - type: array + items: + $ref: "#/components/schemas/Comment" + - type: object + description: No Results + required: + - comments + properties: + comments: + type: array + maxItems: 0 + post: + summary: Create Comment + operationId: createComment + tags: + - Comments + security: + - basicAuth: [] + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + required: + - comment[body] + - comment[post_id] + properties: + comment[body]: + type: string + comment[post_id]: + type: number + comment[do_not_bump_post]: + type: boolean + comment[is_sticky]: + type: boolean + description: Only usable for Janitor+ + comment[is_hidden]: + type: boolean + description: Only usable for Moderator+ + responses: + 201: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/Comment" + 403: + $ref: "#/components/responses/AccessDenied" + 422: + $ref: "#/components/responses/ExpectedError" + /comments/{id}.json: + get: + summary: Get Comment + operationId: getComment + tags: + - Comments + description: If the comment is hidden, you must be the creator or Moderator+ to see it. + parameters: + - name: id + in: path + required: true + description: The ID of the comment. + schema: + type: number + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/Comment" + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + patch: + summary: Edit Comment + operationId: editComment + tags: + - Comments + security: + - basicAuth: [] + description: | + You must be the creator of the comment, or Admin+ to edit. Marked comments cannot be edited. + parameters: + - name: id + in: path + required: true + description: The ID of the comment. + schema: + type: number + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + comment[body]: + type: string + comment[is_sticky]: + type: boolean + description: Only usable for Janitor+ + comment[is_hidden]: + type: boolean + description: Only usable for Moderator+ + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + delete: + summary: Delete Comment + operationId: deleteComment + tags: + - Comments + security: + - basicAuth: [] + description: | + You must be Admin+. + parameters: + - name: id + in: path + required: true + description: The ID of the comment. + schema: + type: number + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + /comments/{id}/hide.json: + post: + summary: Hide Comment + operationId: hideComment + tags: + - Comments + security: + - basicAuth: [] + description: | + You must be the creator or Moderator+. + parameters: + - name: id + in: path + required: true + description: The ID of the comment. + schema: + type: number + responses: + 201: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/Comment" + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + /comments/{id}/unhide.json: + post: + summary: Unhide Comment + operationId: unhideComment + tags: + - Comments + security: + - basicAuth: [] + description: | + You must be Moderator+. + parameters: + - name: id + in: path + required: true + description: The ID of the comment. + schema: + type: number + responses: + 201: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/Comment" + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + /comments/{id}/warning.json: + post: + summary: Mark Comment + operationId: markComment + tags: + - Comments + security: + - basicAuth: [] + description: You must be Moderator+. + parameters: + - name: id + in: path + required: true + description: The ID of the comment. + schema: + type: number + requestBody: + $ref: "#/components/requestBodies/warning" + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/DTextResponse" + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + # Comment Votes + /comments/{id}/votes.json: + post: + summary: Create Comment Vote + operationId: createCommentVote + tags: + - Comment Votes + security: + - basicAuth: [] + parameters: + - name: id + in: path + required: true + description: The ID of the comment. + schema: + type: string + - name: score + in: query + required: true + schema: + type: number + enum: + - -1 + - 1 + - name: no_unvote + in: query + schema: + type: boolean + responses: + 200: + description: Success + content: + application/json: + schema: + type: object + required: + - score + - our_score + properties: + score: + type: number + our_score: + type: number + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + delete: + summary: Delete Comment Vote + operationId: deleteCommentVote + tags: + - Comment Votes + security: + - basicAuth: [] + parameters: + - name: id + in: path + required: true + description: The ID of the comment. + schema: + type: string + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + /comment_votes/lock.json: + post: + summary: Lock Comment Votes + operationId: lockCommentVotes + tags: + - Comment Votes + security: + - basicAuth: [] + description: | + You must be Moderator+. Errors if ids is not provided. + parameters: + - name: ids + in: query + required: true + description: The IDs of the comment votes, comma separated. + schema: + type: string + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + /comment_votes/delete.json: + post: + summary: Delete Comment Vote + operationId: deleteCommentVotes + tags: + - Comment Votes + security: + - basicAuth: [] + description: | + You must be Admin+. Errors if ids is not provided. + parameters: + - name: ids + in: query + required: true + description: The IDs of the comment votes, comma separated. + schema: + type: string + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + # DMails + /dmails.json: + get: + summary: Search DMails + operationId: searchDMails + tags: + - DMails + description: When no results are found, an object with a `dmails` key is returned. + parameters: + - $ref: "#/components/parameters/limit" + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/id" + - in: query + name: search[title_matches] + schema: + type: string + - in: query + name: search[message_matches] + schema: + type: string + - in: query + name: search[to_name] + schema: + type: string + - in: query + name: search[to_id] + schema: + type: number + - in: query + name: search[from_name] + schema: + type: string + - in: query + name: search[from_id] + schema: + type: number + - in: query + name: search[is_read] + schema: + type: boolean + - in: query + name: search[is_deleted] + schema: + type: boolean + - in: query + name: search[read] + schema: + type: boolean + responses: + 200: + description: Success + content: + application/json: + schema: + anyOf: + - type: array + items: + $ref: "#/components/schemas/DMail" + - type: object + description: No Results + required: + - dmails + properties: + dmails: + type: array + maxItems: 0 + /dmails/{id}.json: + get: + summary: Get DMail + operationId: getDMail + tags: + - DMails + description: Fetching a dmail will not mark it as read. + parameters: + - name: id + in: path + required: true + description: The ID of the dmail. + schema: + type: number + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/DMail" + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + delete: + summary: Delete DMail + operationId: deleteDMail + tags: + - DMails + security: + - basicAuth: [] + description: Deleting simply hides your copy of the dmail. + parameters: + - name: id + in: path + required: true + description: The ID of the dmail. + schema: + type: number + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + /dmails/{id}/mark_as_read.json: + put: + summary: Mark DMail As Read + operationId: markDMailAsRead + tags: + - DMails + security: + - basicAuth: [] + parameters: + - name: id + in: path + required: true + description: The ID of the dmail. + schema: + type: number + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + /dmails/{id}/mark_as_unread.json: + put: + summary: Mark DMail As Unread + operationId: markDMailAsUnread + tags: + - DMails + security: + - basicAuth: [] + parameters: + - name: id + in: path + required: true + description: The ID of the dmail. + schema: + type: number + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + /dmails/mark_all_as_unread.json: + put: + summary: Mark All DMails As Unread + operationId: markAllDMailsAsUnread + tags: + - DMails + security: + - basicAuth: [] + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + # DText Preview + /dtext_preview.json: + post: + summary: Preview DText + operationId: previewDText + tags: + - DText + description: Note while this route does not require auth, without auth it requires a CSRF token. For that reason it has been marked as requiring auth. + security: + - basicAuth: [] + requestBody: + content: + application/json: + schema: + type: object + required: + - body + properties: + body: + type: string + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/DTextResponse" + # Email Blacklists + /email_blacklists.json: + get: + summary: Search Email Blacklists + operationId: searchEmailBlacklists + tags: + - Email Blacklists + description: You must be Admin+. When no results are found, an object with an `email_blacklists` key is returned. + parameters: + - $ref: "#/components/parameters/limit" + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/id" + - name: search[order] + in: query + schema: + type: string + enum: + - id_asc + - id_desc + - reason + - domain + - in: query + name: search[domain] + schema: + type: string + - in: query + name: search[reason] + schema: + type: string + responses: + 200: + description: Success + content: + application/json: + schema: + anyOf: + - type: array + items: + $ref: "#/components/schemas/EmailBlacklist" + - type: object + description: No Results + required: + - email_blacklists + properties: + email_blacklists: + type: array + maxItems: 0 + post: + summary: Create Email Blacklist + operationId: createEmailBlacklist + tags: + - Email Blacklists + description: You must be Admin+. + security: + - basicAuth: [] + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + required: + - email_blacklist[domain] + - email_blacklist[reason] + properties: + email_blacklist[domain]: + type: string + email_blacklist[reason]: + type: string + responses: + 201: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/EmailBlacklist" + 422: + $ref: "#/components/responses/ExpectedError" + /email_blacklists/{id}.json: + delete: + summary: Delete Email Blacklist + operationId: deleteEmailBlacklist + tags: + - Email Blacklists + security: + - basicAuth: [] + description: You must be Admin+. + parameters: + - name: id + in: path + required: true + description: The ID of the email blacklist. + schema: + type: number + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + # Favorites + /favorites.json: + get: + summary: List Favorites + operationId: listFavorites + tags: + - Favorites + parameters: + - $ref: "#/components/parameters/limit" + - $ref: "#/components/parameters/page" + - in: query + name: user_id + description: You must be the user or Moderator+ if the user has their favorites hidden. + schema: + type: number + responses: + 200: + description: Success + content: + application/json: + schema: + type: object + required: + - posts + properties: + posts: + type: array + items: + $ref: "#/components/schemas/Post" + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + post: + summary: Add Favorite + operationId: addFavorite + tags: + - Favorites + security: + - basicAuth: [] + requestBody: + content: + application/json: + schema: + type: object + required: + - post_id + properties: + post_ud: + type: number + responses: + 201: + description: Success + content: + application/json: + schema: + type: object + required: + - post + properties: + post: + $ref: "#/components/schemas/Post" + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + /favorites/{id}.json: + delete: + summary: Remove Favorite + operationId: removeFavorite + tags: + - Favorites + security: + - basicAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: number + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + # Forum Post + /forum_posts.json: + get: + summary: Search Forum Posts + operationId: searchForumPosts + tags: + - Forum Posts + description: When no results are found, an object with an `forum_posts` key is returned. + parameters: + - $ref: "#/components/parameters/limit" + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/id" + - name: search[order] + in: query + schema: + type: string + enum: + - id_asc + - id_desc + - in: query + name: search[creator_id] + schema: + type: number + - in: query + name: search[creator_name] + schema: + type: string + - in: query + name: search[topic_id] + schema: + type: number + - in: query + name: search[topic_title_matches] + schema: + type: string + - in: query + name: search[body_matches] + schema: + type: string + - in: query + name: search[topic_category_id] + schema: + type: number + - in: query + name: search[is_hidden] + schema: + type: boolean + responses: + 200: + description: Success + content: + application/json: + schema: + anyOf: + - type: array + items: + $ref: "#/components/schemas/ForumPost" + - type: object + description: No Results + required: + - forum_posts + properties: + forum_posts: + type: array + maxItems: 0 + post: + summary: Create Forum Post + operationId: createForumPost + tags: + - Forum Posts + security: + - basicAuth: [] + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + required: + - forum_post[body] + - forum_post[topic_id] + properties: + forum_post[body]: + type: string + forum_post[topic_id]: + type: number + responses: + 201: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/ForumPost" + 403: + $ref: "#/components/responses/AccessDenied" + 422: + $ref: "#/components/responses/ExpectedError" + /forum_posts/{id}.json: + get: + summary: Get Forum Post + operationId: getForumPost + tags: + - Forum Posts + description: If the forum post is hidden, you must be the creator or Moderator+ to see it. + parameters: + - name: id + in: path + required: true + description: The ID of the forum post. + schema: + type: number + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/ForumPost" + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + patch: + summary: Edit Forum Post + operationId: editForumPost + tags: + - Forum Posts + security: + - basicAuth: [] + description: You must be the creator of the forum post, or Admin+ to edit. Marked forum posts cannot be edited. + parameters: + - name: id + in: path + required: true + description: The ID of the forum post. + schema: + type: number + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + forum_post[body]: + type: string + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + delete: + summary: Delete Forum Post + operationId: deleteForumPost + tags: + - Forum Posts + security: + - basicAuth: [] + description: You must be Admin+. + parameters: + - name: id + in: path + required: true + description: The ID of the forum post. + schema: + type: number + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + /forum_posts/{id}/hide.json: + post: + summary: Hide Forum Post + operationId: hideForumPost + tags: + - Forum Posts + security: + - basicAuth: [] + description: You must be the creator or Moderator+. + parameters: + - name: id + in: path + required: true + description: The ID of the forum post. + schema: + type: number + responses: + 201: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/ForumPost" + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + /forum_posts/{id}/unhide.json: + post: + summary: Unhide Forum Post + operationId: unhideForumPost + tags: + - Forum Posts + security: + - basicAuth: [] + description: You must be Moderator+. + parameters: + - name: id + in: path + required: true + description: The ID of the forum post. + schema: + type: number + responses: + 201: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/ForumPost" + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + /forum_posts/{id}/warning.json: + post: + summary: Mark Forum Post + operationId: markForumPost + tags: + - Forum Posts + security: + - basicAuth: [] + description: | + You must be Moderator+. + parameters: + - name: id + in: path + required: true + description: The ID of the forum post. + schema: + type: number + requestBody: + $ref: "#/components/requestBodies/warning" + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/DTextResponse" + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + # Forum Post Votes + /forum_posts/{id}/votes.json: + post: + summary: Create Forum Post Vote + operationId: createForumPostVote + tags: + - Forum Post Votes + security: + - basicAuth: [] + parameters: + - name: id + in: path + required: true + description: The ID of the forum post. + schema: + type: string + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + required: + - forum_post_vote[score] + properties: + forum_post_vote[score]: + type: number + enum: + - -1 + - 0 + - 1 + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/ForumPostVote" + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + delete: + summary: Delete Forum Post Vote + operationId: deleteForumPostVote + tags: + - Forum Post Votes + security: + - basicAuth: [] + parameters: + - name: id + in: path + required: true + description: The ID of the forum post. + schema: + type: string + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + # Forum Topics + /forum_topics.json: + get: + summary: Search Forum Topics + operationId: searchForumTopics + tags: + - Forum Topics + parameters: + - $ref: "#/components/parameters/limit" + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/id" + - name: search[order] + in: query + schema: + type: string + enum: + - id_asc + - id_desc + - sticky + - in: query + name: search[title] + schema: + type: string + - in: query + name: search[title_matches] + schema: + type: string + - in: query + name: search[category_id] + schema: + type: number + - in: query + name: search[is_sticky] + schema: + type: boolean + - in: query + name: search[is_locked] + schema: + type: boolean + - in: query + name: search[is_hidden] + schema: + type: boolean + responses: + 200: + description: Success + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/ForumTopic" + post: + summary: Create Forum Topic + operationId: createForumTopic + tags: + - Forum Topics + security: + - basicAuth: [] + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + required: + - forum_topic[title] + - forum_topic[category_id] + properties: + forum_topic[title]: + type: string + forum_topic[category_id]: + type: number + forum_topic[original_post_attributes][id]: + type: number + description: Forum post ID. Mutually exclusive with body, one must be provided. + forum_topic[original_post_attributes][body]: + type: string + description: First forum post body. Mutually exclusive with id, one must be provided. + forum_topic[is_sticky]: + type: boolean + description: You must be Moderator+. + forum_topic[is_locked]: + type: boolean + description: You must be Moderator+. + responses: + 201: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/ForumTopic" + 403: + $ref: "#/components/responses/AccessDenied" + 422: + $ref: "#/components/responses/ExpectedError" + /forum_topics/{id}.json: + get: + summary: Get Forum Forum Topic + operationId: getForumTopic + tags: + - Forum Topics + description: If the forum topic is hidden, you must be the creator or Moderator+ to see it. + parameters: + - name: id + in: path + required: true + description: The ID of the forum topic. + schema: + type: number + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/ForumTopic" + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + patch: + summary: Edit Forum Topic + operationId: editForumTopic + tags: + - Forum Topics + security: + - basicAuth: [] + parameters: + - name: id + in: path + required: true + description: The ID of the forum topic. + schema: + type: number + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + forum_topic[title]: + type: string + forum_topic[category_id]: + type: number + forum_topic[original_post_attributes][id]: + type: number + description: Forum post ID. Silently ignored + forum_topic[original_post_attributes][body]: + type: string + description: First forum post body. + forum_topic[is_sticky]: + type: boolean + description: You must be Moderator+. + forum_topic[is_locked]: + type: boolean + description: You must be Moderator+. + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + delete: + summary: Delete Forum Topic + operationId: deleteForumTopic + tags: + - Forum Topics + security: + - basicAuth: [] + description: You must be Admin+. + parameters: + - name: id + in: path + required: true + description: The ID of the forum topic. + schema: + type: number + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + /forum_topics/{id}/hide.json: + post: + summary: Hide Forum Topic + operationId: hideForumTopic + tags: + - Forum Topics + security: + - basicAuth: [] + description: You must be the creator or Moderator+. + parameters: + - name: id + in: path + required: true + description: The ID of the forum topic. + schema: + type: number + responses: + 201: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/ForumTopic" + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + /forum_topics/{id}/unhide.json: + post: + summary: Unhide Forum Topic + operationId: unhideForumTopic + tags: + - Forum Topics + security: + - basicAuth: [] + description: You must be Moderator+. + parameters: + - name: id + in: path + required: true + description: The ID of the forum topic. + schema: + type: number + responses: + 201: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/ForumTopic" + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + /forum_topics/{id}/subscribe.json: + post: + summary: Subscribe To Forum Topic + operationId: subscribeForumTopic + tags: + - Forum Topics + security: + - basicAuth: [] + parameters: + - name: id + in: path + required: true + description: The ID of the forum topic. + schema: + type: number + responses: + 201: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/ForumTopic" + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + /forum_topics/{id}/unsubscribe.json: + post: + summary: Unsubscribe From Forum Topic + operationId: unsubscribeForumTopic + tags: + - Forum Topics + security: + - basicAuth: [] + parameters: + - name: id + in: path + required: true + description: The ID of the forum topic. + schema: + type: number + responses: + 201: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/ForumTopic" + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + /forum_topics/mark_all_as_read.json: + post: + summary: Mark All Forum Topics As Read + operationId: markAllForumTopicsAsRead + tags: + - Forum Topics + security: + - basicAuth: [] + requestBody: + content: + application/json: + schema: + type: object + required: + - category_id + properties: + category_id: + type: number + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + # Help Pages + /help.json: + get: + summary: List Help Pages + operationId: listHelpPages + description: Will error if no help pages exist. + tags: + - Help Pages + responses: + 200: + description: Success + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Help" + post: + summary: Create Help Page + operationId: createHelpPage + tags: + - Help Pages + security: + - basicAuth: [] + description: You must be Admin+. + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + required: + - help_page[name] + - help_page[wiki_page] + properties: + help_page[name]: + type: string + help_page[wiki_page]: + type: string + help_page[related]: + type: string + description: Separate with a comma followed by a space. + help_page[title]: + type: string + responses: + 201: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/Help" + 403: + $ref: "#/components/responses/AccessDenied" + 422: + $ref: "#/components/responses/ExpectedError" + /help/{id}.json: + get: + summary: Get Help Page + operationId: getHelpPage + tags: + - Help Pages + parameters: + - name: id + in: path + required: true + description: The ID or name of the help page. + schema: + type: ["string", "number"] + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/Help" + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + patch: + summary: Edit Help Page + operationId: editHelpPage + tags: + - Help Pages + security: + - basicAuth: [] + description: You must be Admin+ + parameters: + - name: id + in: path + required: true + description: The ID of the help page. + schema: + type: number + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + help_page[name]: + type: string + help_page[wiki_page]: + type: string + help_page[related]: + type: string + description: Separate with a comma followed by a space. + help_page[title]: + type: string + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + delete: + summary: Delete Help Page + operationId: deleteHelpPage + tags: + - Help Pages + security: + - basicAuth: [] + description: You must be Admin+. + parameters: + - name: id + in: path + required: true + description: The ID of the help page. + schema: + type: number + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + # IP Bans + /ip_bans.json: + get: + summary: List IP Bans + operationId: listIPBans + tags: + - IP Bans + security: + - basicAuth: [] + description: You must be Admin+. When no results are found, an object with an `ip_bans` key is returned. + parameters: + - $ref: "#/components/parameters/limit" + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/id" + - $ref: "#/components/parameters/ip_addr" + - name: search[order] + in: query + schema: + type: string + enum: + - id_asc + - id_desc + - in: query + name: search[banner_id] + schema: + type: number + - in: query + name: search[banner_name] + schema: + type: string + - in: query + name: search[reason] + schema: + type: string + responses: + 200: + description: Success + content: + application/json: + schema: + anyOf: + - type: array + items: + $ref: "#/components/schemas/IPBan" + - type: object + description: No Results + required: + - ip_bans + properties: + ip_bans: + type: array + maxItems: 0 + post: + summary: Create IP Ban + operationId: createIPBan + tags: + - IP Bans + security: + - basicAuth: [] + description: You must be Admin+. + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + required: + - ip_ban[ip_addr] + - ip_ban[reason] + properties: + ip_ban[ip_addr]: + type: string + ip_ban[reason]: + type: string + responses: + 201: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/IPBan" + 403: + $ref: "#/components/responses/AccessDenied" + 422: + $ref: "#/components/responses/ExpectedError" + /ip_bans/{id}.json: + delete: + summary: Delete IP Ban + operationId: deleteIPBan + tags: + - IP Bans + security: + - basicAuth: [] + description: You must be Admin+. + parameters: + - name: id + in: path + required: true + description: The ID of the ip ban. + schema: + type: number + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + # IQDB + /iqdb_queries.json: + get: + summary: Query IQDB + operationId: queryIQDBGet + tags: + - IQDB + parameters: + - name: search[score_cutoff] + in: query + schema: + type: number + - name: search[url] + in: query + schema: + type: string + - name: search[post_id] + in: query + schema: + type: number + - name: search[hash] + in: query + schema: + type: string + responses: + 200: + description: Success + content: + application/json: + schema: + type: object + required: + - posts + properties: + posts: + type: array + items: + $ref: "#/components/schemas/IQDBResponse" + 403: + $ref: "#/components/responses/AccessDenied" + post: + summary: Query IQDB + operationId: queryIQDPost + tags: + - IQDB + requestBody: + content: + multipart/form-data: + schema: + type: object + properties: + search[file]: + type: string + format: binary + search[score_cutoff]: + type: number + search[url]: + type: string + search[post_id]: + type: string + search[hash]: + type: string + application/json: + schema: + type: object + properties: + score_cutoff: + type: number + url: + type: string + post_id: + type: string + hash: + type: string + responses: + 200: + description: Success + content: + application/json: + schema: + type: object + required: + - posts + properties: + posts: + type: array + items: + $ref: "#/components/schemas/IQDBResponse" + 403: + $ref: "#/components/responses/AccessDenied" + # Mascots + /mascots.json: + get: + summary: Search Mascots + operationId: searchMascots + tags: + - Mascots + description: When no results are found, an object with an `mascots` key is returned. + parameters: + - $ref: "#/components/parameters/limit" + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/id" + responses: + 200: + description: Success + content: + application/json: + schema: + anyOf: + - type: array + items: + $ref: "#/components/schemas/Mascot" + - type: object + description: No Results + required: + - mascots + properties: + mascots: + type: array + maxItems: 0 + post: + summary: Create Mascot + operationId: createMascot + tags: + - Mascots + security: + - basicAuth: [] + description: You must be Admin+. + requestBody: + content: + multipart/form-data: + schema: + type: object + required: + - mascot[mascot_file] + - mascot[display_name] + - mascot[background_color] + - mascot[artist_url] + - mascot[artist_name] + properties: + mascot[mascot_file]: + type: string + format: binary + mascot[display_name]: + type: string + mascot[background_color]: + type: string + mascot[artist_url]: + type: string + mascot[artist_name]: + type: string + mascot[available_on_string]: + type: string + description: Comma separated site names. + mascot[active]: + type: boolean + mascot[hide_anonymous]: + type: boolean + responses: + 201: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/Mascot" + 403: + $ref: "#/components/responses/AccessDenied" + 422: + $ref: "#/components/responses/ExpectedError" + /mascots/{id}.json: + patch: + summary: Edit Mascot + operationId: editMascot + tags: + - Mascots + security: + - basicAuth: [] + description: You must be Admin+. + parameters: + - name: id + in: path + required: true + description: The ID of the mascot. + schema: + type: number + requestBody: + content: + multipart/form-data: + schema: + type: object + properties: + mascot[mascot_file]: + type: string + format: binary + mascot[display_name]: + type: string + mascot[background_color]: + type: string + mascot[artist_url]: + type: string + mascot[artist_name]: + type: string + mascot[available_on_string]: + type: string + description: Comma separated site names. + mascot[active]: + type: boolean + mascot[hide_anonymous]: + type: boolean + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + delete: + summary: Delete Mascot + operationId: deleteMascot + tags: + - Mascots + security: + - basicAuth: [] + description: You must be Admin+. + parameters: + - name: id + in: path + required: true + description: The ID of the mascot. + schema: + type: number + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + # Mod Actions + /mod_actions.json: + get: + summary: Search Mod Actions + operationId: searchModActions + tags: + - Mod Actions + description: | + Note that some mod actions are not in use anymore. Their entries exist for historical purposes only.
+ The current legacy actions are as follows:
+ * `created_positive_record`, `created_neutral_record`, `created_negative_record` + * `created_flag_reason`, `edited_flag_reason`, `deleted_flag_reason` + * `post_move_favorites`, `post_delete`, `post_undelete`, `post_destroy`, `post_rating_lock`, `post_unapprove` + * `post_replacement_accept`, `post_replacement_reject`, `post_replacement_delete` + parameters: + - $ref: "#/components/parameters/limit" + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/id" + - name: search[order] + in: query + schema: + type: string + enum: + - id_asc + - id_desc + - in: query + name: search[creator_id] + schema: + type: number + - in: query + name: search[creator_name] + schema: + type: string + - in: query + name: search[action] + schema: + type: string + enum: *modaction-actions + responses: + 200: + description: Success + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/ModAction" + # News Updates + /news_updates.json: + get: + summary: List News Updates + operationId: listNewsUpdates + tags: + - News Updates + description: When no results are found, an object with an `news_updates` key is returned. + parameters: + - $ref: "#/components/parameters/limit" + - $ref: "#/components/parameters/page" + responses: + 200: + description: Success + content: + application/json: + schema: + anyOf: + - type: array + items: + $ref: "#/components/schemas/NewsUpdate" + - type: object + description: No Results + required: + - news_updates + properties: + news_updates: + type: array + maxItems: 0 + post: + summary: Create News Update + operationId: createNewsUpdate + tags: + - News Updates + security: + - basicAuth: [] + description: You must be Admin+. + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + required: + - news_update[message] + properties: + news_update[message]: + type: string + responses: + 201: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/NewsUpdate" + 403: + $ref: "#/components/responses/AccessDenied" + 422: + $ref: "#/components/responses/ExpectedError" + /news_updates/{id}.json: + patch: + summary: Edit News Update + operationId: editNewsUpdate + tags: + - News Updates + security: + - basicAuth: [] + description: You must be Admin+ + parameters: + - name: id + in: path + required: true + description: The ID of the news update. + schema: + type: number + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + required: + - news_update[message] + properties: + news_update[message]: + type: string + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + delete: + summary: Delete News Update + operationId: deleteNewsUpdate + tags: + - News Updates + security: + - basicAuth: [] + description: You must be Admin+. + parameters: + - name: id + in: path + required: true + description: The ID of the news update. + schema: + type: number + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + # Notes + /notes.json: + get: + summary: Search Notes + operationId: searchNotes + tags: + - Notes + description: When no results are found, an object with an `notes` key is returned. + parameters: + - $ref: "#/components/parameters/limit" + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/id" + - name: search[order] + in: query + schema: + type: string + enum: + - id_asc + - id_desc + - in: query + name: search[body_matches] + schema: + type: string + - in: query + name: search[is_active] + schema: + type: boolean + - in: query + name: search[post_id] + schema: + type: number + - in: query + name: search[post_tags_match] + schema: + type: string + - in: query + name: search[post_note_updater_id] + schema: + type: number + - in: query + name: search[post_note_updater_name] + schema: + type: string + - in: query + name: search[creator_id] + schema: + type: number + - in: query + name: search[creator_name] + schema: + type: string + responses: + 200: + description: Success + content: + application/json: + schema: + anyOf: + - type: array + items: + $ref: "#/components/schemas/Note" + - type: object + description: No Results + required: + - notes + properties: + notes: + type: array + maxItems: 0 + post: + summary: Create Note + operationId: createNote + tags: + - Notes + security: + - basicAuth: [] + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + required: + - note[post_id] + - note[x] + - note[y] + - note[width] + - note[height] + - note[body] + properties: + note[post_id]: + type: number + note[x]: + type: number + note[y]: + type: number + note[width]: + type: number + note[height]: + type: number + note[body]: + type: string + note[html_id]: + type: string + description: Passthrough, used in frontend. + responses: + 201: + description: Success + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/Note" + - type: object + required: + - html_id + properties: + html_id: + type: string + description: Passthrough, used in frontend. + 403: + $ref: "#/components/responses/AccessDenied" + 422: + $ref: "#/components/responses/ExpectedError" + /notes/{id}.json: + get: + summary: Get Note + operationId: getNote + tags: + - Notes + parameters: + - name: id + in: path + required: true + description: The ID of the note. + schema: + type: number + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/Note" + 404: + $ref: "#/components/responses/NotFound" + patch: + summary: Edit Note + operationId: editNote + tags: + - Notes + security: + - basicAuth: [] + parameters: + - name: id + in: path + required: true + description: The ID of the note. + schema: + type: number + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + note[x]: + type: number + note[y]: + type: number + note[width]: + type: number + note[height]: + type: number + note[body]: + type: string + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + delete: + summary: Delete Note + operationId: deleteNote + tags: + - Notes + security: + - basicAuth: [] + parameters: + - name: id + in: path + required: true + description: The ID of the note. + schema: + type: number + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + /notes/{id}/revert.json: + post: + summary: Revert Note + operationId: revertNote + tags: + - Notes + security: + - basicAuth: [] + parameters: + - name: id + in: path + required: true + description: The ID of the note. + schema: + type: number + - name: version_id + in: query + required: true + description: The version ID to revert to. + schema: + type: number + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + # Note Versions + /note_versions.json: + get: + summary: Search Note Versions + operationId: searchNoteVersions + tags: + - Note Versions + description: When no results are found, an object with an `note_versions` key is returned. + parameters: + - $ref: "#/components/parameters/limit" + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/id" + - $ref: "#/components/parameters/ip_addr" + - name: search[order] + in: query + description: The order of the results. + schema: + type: string + enum: + - id_asc + - id_desc + - in: query + name: search[updater_id] + schema: + type: number + - in: query + name: search[updater_name] + schema: + type: string + - in: query + name: search[post_id] + schema: + type: number + - in: query + name: search[note_id] + schema: + type: number + - in: query + name: search[is_active] + schema: + type: boolean + - in: query + name: search[body_matches] + schema: + type: string + responses: + 200: + description: Success + content: + application/json: + schema: + anyOf: + - type: array + items: + $ref: "#/components/schemas/NoteVersion" + - type: object + description: No Results + required: + - note_versions + properties: + note_versions: + type: array + maxItems: 0 + # Pools + /pools.json: + get: + summary: Search Pools + operationId: searchPools + tags: + - Pools + parameters: + - $ref: "#/components/parameters/limit" + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/id" + - name: search[order] + in: query + schema: + type: string + enum: + - id_asc + - id_desc + - name + - created_at + - post_count + - in: query + name: search[name_matches] + schema: + type: string + - in: query + name: search[description_matches] + schema: + type: string + - in: query + name: search[creator_id] + schema: + type: number + - in: query + name: search[creator_name] + schema: + type: string + - in: query + name: search[category] + schema: + type: string + enum: *pool-categories + - in: query + name: search[is_active] + schema: + type: boolean + responses: + 200: + description: Success + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Pool" + post: + summary: Create Pool + operationId: createPool + tags: + - Pools + security: + - basicAuth: [] + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + required: + - pool[name] + properties: + pool[name]: + type: string + pool[description]: + type: string + pool[category]: + type: string + enum: *pool-categories + ipool[s_active]: + type: boolean + pool[post_ids_string]: + type: string + description: Space separated list of post IDs. Mutually exclusive with post_ids. + pool[post_ids]: + type: array + description: Array of post IDs. Mutually exclusive with post_ids_string. + items: + type: number + responses: + 201: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/Pool" + 403: + $ref: "#/components/responses/AccessDenied" + 422: + $ref: "#/components/responses/ExpectedError" + /pools/{id}.json: + get: + summary: Get Pool + operationId: getPool + tags: + - Pools + parameters: + - name: id + in: path + required: true + description: The ID of the pool. + schema: + type: number + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/Pool" + 404: + $ref: "#/components/responses/NotFound" + patch: + summary: Edit Pool + operationId: editPool + tags: + - Pools + security: + - basicAuth: [] + parameters: + - name: id + in: path + required: true + description: The ID of the pool. + schema: + type: number + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + pool[name]: + type: string + pool[description]: + type: string + pool[is_active]: + type: boolean + pool[category]: + type: string + enum: *pool-categories + description: If the pool has more than 30 posts, you must be Janitor+. + pool[post_ids_string]: + type: string + description: Space separated list of post IDs. Mutually exclusive with post_ids. + pool[post_ids]: + type: array + description: Array of post IDs. Mutually exclusive with post_ids_string. + items: + type: number + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + delete: + summary: Delete Pool + operationId: deletePool + tags: + - Pools + description: You must be Janitor+. + security: + - basicAuth: [] + parameters: + - name: id + in: path + required: true + description: The ID of the pool. + schema: + type: number + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + /pools/{id}/revert.json: + put: + summary: Revert Pool + operationId: revertPool + tags: + - Pools + security: + - basicAuth: [] + parameters: + - name: id + in: path + required: true + description: The ID of the pool. + schema: + type: number + - name: version_id + in: query + required: true + description: The version ID to revert to. + schema: + type: number + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + # Pool Element + /pool_element.js: + post: + summary: Add Post To Pool + operationId: addPostToPool + tags: + - Pools + security: + - basicAuth: [] + description: Note that the extension is JS, this route will not return JSON. + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + required: + - post_id + properties: + pool_id: + type: number + description: Mutually exclusive with pool_name. + pool_name: + type: string + description: Mutually exclusive with pool_id. + post_id: + type: number + responses: + 200: + description: Success + /pool_element.json: + delete: + summary: Remove Post From Pool + operationId: removePostFromPool + tags: + - Pools + security: + - basicAuth: [] + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + required: + - pool_id + - post_id + properties: + pool_id: + type: number + post_id: + type: number + responses: + 204: + description: Success + # Pool Versions + /pool_versions.json: + get: + summary: Search Pool Versions + operationId: searchPoolVersions + tags: + - Pool Versions + description: When no results are found, an object with an `pool_versions` key is returned. + parameters: + - $ref: "#/components/parameters/limit" + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/id" + - $ref: "#/components/parameters/ip_addr" + - name: search[order] + in: query + description: The order of the results. + schema: + type: string + enum: + - id_asc + - id_desc + - in: query + name: search[updater_id] + schema: + type: number + - in: query + name: search[updater_name] + schema: + type: string + - in: query + name: search[pool_id] + schema: + type: number + responses: + 200: + description: Success + content: + application/json: + schema: + anyOf: + - type: array + items: + $ref: "#/components/schemas/PoolVersion" + - type: object + description: No Results + required: + - pool_versions + properties: + pool_versions: + type: array + maxItems: 0 + # Popular + /popular.json: + get: + summary: List Most Upvoted Posts + operationId: listPopular + tags: + - Popular + parameters: + - $ref: "#/components/parameters/limit" + - name: date + in: query + description: The date to list popular uploads for. Only The day, month, and year are considered. + schema: + type: string + format: date + - name: scale + in: query + description: The scale of the results, in relation to `date`. + schema: + type: string + enum: + - month + - week + - day + responses: + 200: + description: Success + content: + application/json: + schema: + type: object + required: + - posts + properties: + posts: + type: array + items: + $ref: "#/components/schemas/Post" + # Posts + /posts.json: + get: + summary: Search Posts + operationId: searchPosts + tags: + - Posts + parameters: + - $ref: "#/components/parameters/limit" + - $ref: "#/components/parameters/page" + - in: query + name: tags + schema: + type: string + - in: query + name: md5 + schema: + type: string + - in: query + name: random + schema: + type: string + responses: + 200: + description: Success + content: + application/json: + schema: + type: object + required: + - posts + properties: + posts: + type: array + items: + $ref: "#/components/schemas/Post" + /posts/{id}.json: + get: + summary: Get Post + operationId: getPost + tags: + - Posts + parameters: + - name: id + in: path + required: true + description: The ID of the post. + schema: + type: number + responses: + 200: + description: Success + content: + application/json: + schema: + type: object + required: + - post + properties: + post: + $ref: "#/components/schemas/Post" + 404: + $ref: "#/components/responses/NotFound" + patch: + summary: Edit Post + operationId: editPost + tags: + - Posts + security: + - basicAuth: [] + description: Most errors are silently swallowed. + parameters: + - name: id + in: path + required: true + description: The ID of the post. + schema: + type: number + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + post[tag_string]: + type: string + description: Replaces all tags on the post. + post[old_tag_string]: + type: string + description: The tag string before your edits, used to reconcile conflicts. + post[tag_string_diff]: + type: string + description: Tags with a minus are removed, else they are added. Mutually exclusive with tag_string. + post[source_diff]: + type: string + description: Sources with a minus are removed, else they are added. It is not possible to add inactive sources through this. Mutually exclusive with source. + post[source]: + type: string + description: Replaces all sources on the post. + post[old_source]: + type: string + description: The sources before your edits, used to reconcile conflicts. + post[parent_id]: + type: number + post[old_parent_id]: + type: number + post[description]: + type: string + post[old_description]: + type: string + post[rating]: + type: string + enum: *ratings + post[old_rating]: + type: string + enum: *ratings + post[edit_reason]: + type: string + post[is_rating_locked]: + type: boolean + description: You must be Privileged+. + post[is_note_locked]: + type: boolean + description: You must be Janitor+. + post[bg_colo]r: + type: string + description: You must be Janitor+. + post[is_comment_locked]: + type: boolean + description: You must be Admin+. + post[is_status_locked]: + type: boolean + description: You must be Admin+. + post[locked_tags]: + type: string + description: You must be Admin+. + post[hide_from_anonymous]: + type: boolean + description: You must be Admin+. + post[hide_from_search_engines]: + type: boolean + description: You must be Admin+. + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/Post" + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + /posts/{id}/update_iqdb.json: + get: + summary: Update Post IQDB + operationId: updatePostIqdb + tags: + - Posts + security: + - basicAuth: [] + description: You must be Admin+. + parameters: + - name: id + in: path + required: true + description: The ID of the post. + schema: + type: number + responses: + 200: + description: Success + content: + application/json: + schema: + type: object + required: + - post + properties: + post: + $ref: "#/components/schemas/Post" + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + /posts/{id}/mark_as_translated.json: + post: + summary: Mark Post As Translated + operationId: markPostAsTranslated + tags: + - Posts + security: + - basicAuth: [] + description: Will error if no body is provided. + parameters: + - name: id + in: path + required: true + description: The ID of the post. + schema: + type: number + requestBody: + content: + application/json: + schema: + type: object + properties: + translation_check: + type: boolean + partially_translated: + type: boolean + responses: + 200: + description: Success + content: + application/json: + schema: + type: object + required: + - post + properties: + post: + $ref: "#/components/schemas/Post" + + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + /posts/{id}/copy_notes.json: + put: + summary: Copy Notes To Post + operationId: copyNotesToPost + tags: + - Posts + security: + - basicAuth: [] + parameters: + - name: id + in: path + required: true + description: The ID of the post. + schema: + type: number + requestBody: + content: + application/json: + schema: + type: object + required: + - other_post_id + properties: + other_post_id: + type: number + responses: + 204: + description: Success + 400: + $ref: "#/components/responses/MessageError" + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + /posts/{id}/revert.json: + post: + summary: Revert Post + operationId: revertPost + tags: + - Posts + security: + - basicAuth: [] + parameters: + - name: id + in: path + required: true + description: The ID of the post. + schema: + type: number + - name: version_id + in: query + required: true + description: The version ID to revert to. + schema: + type: number + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + /posts/{id}/show_seq.json: + get: + summary: Get Post In Sequence + operationId: getPostInSequence + tags: + - Posts + parameters: + - name: id + in: path + required: true + description: The ID of the post. + schema: + type: number + - name: seq + in: query + description: The direction to move in the sequence. + schema: + type: string + enum: + - next + - prev + responses: + 200: + description: Success + content: + application/json: + schema: + type: object + required: + - post + properties: + post: + $ref: "#/components/schemas/Post" + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + /posts/{id}/flag.json: + delete: + summary: Unflag Post + operationId: unflagPost + tags: + - Posts + description: You must have the "Approve Posts" permission. + security: + - basicAuth: [] + parameters: + - name: id + in: path + required: true + description: The ID of the post. + schema: + type: number + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + approval: + type: string + description: Approves the post if set to "approve". + + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + /posts/random.json: + get: + summary: Get Random Post + operationId: getRandomPost + tags: + - Posts + parameters: + - name: tags + in: query + schema: + type: string + responses: + 200: + description: Success + content: + application/json: + schema: + type: object + required: + - post + properties: + post: + $ref: "#/components/schemas/Post" + 404: + $ref: "#/components/responses/NotFound" + # Post Sets + /post_sets.json: + get: + summary: Search Post Sets + operationId: searchPostSets + tags: + - Post Sets + description: When no results are found, an object with a `post_sets` key is returned. + parameters: + - $ref: "#/components/parameters/limit" + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/id" + - name: search[order] + in: query + schema: + type: string + enum: + - id_asc + - id_desc + - name + - shortname + - post_count + - postcount + - created_at + - updated_at + - update + - in: query + name: search[creator_id] + schema: + type: number + - in: query + name: search[creator_name] + schema: + type: string + - in: query + name: search[name] + schema: + type: string + - in: query + name: search[shortname] + schema: + type: string + - in: query + name: search[is_public] + description: You must be Moderator+. + schema: + type: boolean + - in: query + name: search[post_id] + schema: + type: number + - in: query + name: search[maintainer_id] + schema: + type: number + responses: + 200: + description: Success + content: + application/json: + schema: + anyOf: + - type: array + items: + $ref: "#/components/schemas/PostSet" + - type: object + description: No Results + required: + - post_sets + properties: + post_sets: + type: array + maxItems: 0 + post: + summary: Create Post Set + operationId: createPostSet + tags: + - Post Sets + security: + - basicAuth: [] + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + required: + - post_set[name] + - post_set[shortname] + properties: + post_set[name]: + type: string + post_set[shortname]: + type: string + post_set[description]: + type: string + post_set[is_public]: + type: boolean + post_set[ransfer_on_delete]: + type: boolean + responses: + 201: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/PostSet" + 403: + $ref: "#/components/responses/AccessDenied" + 422: + $ref: "#/components/responses/ExpectedError" + /post_sets/{id}.json: + get: + summary: Get Post Set + operationId: getPostSet + tags: + - Post Sets + description: You must be Moderator+ if the set is not public. + parameters: + - name: id + in: path + required: true + description: The ID of the post set. + schema: + type: number + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/PostSet" + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + patch: + summary: Edit Post Set + operationId: editPostSet + tags: + - Post Sets + security: + - basicAuth: [] + description: You must be the owner of the set, or Admin+. + parameters: + - name: id + in: path + required: true + description: The ID of the post sets. + schema: + type: number + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + post_set[name]: + type: string + post_set[shortname]: + type: string + post_set[description]: + type: string + post_set[is_public]: + type: boolean + post_set[transfer_on_delete]: + type: boolean + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + delete: + summary: Delete Post Set + operationId: deletePostSet + tags: + - Post Sets + security: + - basicAuth: [] + description: You must be the owner of the set, or Admin+. + parameters: + - name: id + in: path + required: true + description: The ID of the post set. + schema: + type: number + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + /post_sets/{id}/update_posts.json: + post: + summary: Update Post Set Posts + operationId: updatePostSetPosts + tags: + - Post Sets + security: + - basicAuth: [] + description: You must be the owner of the set, a maintainer (if public), or Admin+. + parameters: + - name: id + in: path + required: true + description: The ID of the post set. + schema: + type: number + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + required: + - post_set[post_ids_string] + properties: + post_set[post_ids_string]: + type: string + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/PostSet" + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + /post_sets/{id}/add_posts.json: + post: + summary: Add Posts To Post Set + operationId: addPostsToPostSet + tags: + - Post Sets + security: + - basicAuth: [] + description: You must be the owner of the set, a maintainer (if public), or Admin+. + parameters: + - name: id + in: path + required: true + description: The ID of the post set. + schema: + type: number + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + required: + - post_ids + properties: + post_ids: + type: array + description: post_ids[]=1&post_ids[]=2 + items: + type: number + responses: + 201: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/PostSet" + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + /post_sets/{id}/remove_posts.json: + post: + summary: Remove Posts From Post Set + operationId: removePostsFromPostSet + tags: + - Post Sets + security: + - basicAuth: [] + description: You must be the owner of the set, a maintainer (if public), or Admin+. + parameters: + - name: id + in: path + required: true + description: The ID of the post set. + schema: + type: number + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + required: + - post_ids + properties: + post_ids: + type: array + description: post_ids[]=1&post_ids[]=2 + items: + type: number + responses: + 201: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/PostSet" + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + /post_sets/for_select.json: + get: + summary: List Post Sets For Select + operationId: listPostSetsForSelect + tags: + - Post Sets + security: + - basicAuth: [] + description: You must be the owner of the set, a maintainer (if public), or Admin+. + responses: + 200: + description: Success + content: + application/json: + schema: + type: object + properties: + Owned: + type: array + items: + anyOf: + - type: string + - type: number + Maintained: + type: array + items: + anyOf: + - type: string + - type: number + 403: + $ref: "#/components/responses/AccessDenied" + 422: + $ref: "#/components/responses/ExpectedError" + # Post Approvals + /post_approvals.json: + get: + summary: Search Post Approvals + operationId: searchPostApprovals + tags: + - Post Approvals + description: When no results are found, an object with an `post_approvals` key is returned. + parameters: + - $ref: "#/components/parameters/limit" + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/id" + - name: search[order] + in: query + schema: + type: string + enum: + - id_asc + - id_desc + - name: search[post_tags_match] + in: query + schema: + type: string + - name: search[user_id] + in: query + schema: + type: number + - name: search[user_name] + in: query + schema: + type: string + - name: search[post_id] + in: query + schema: + type: number + responses: + 200: + description: Success + content: + application/json: + schema: + anyOf: + - type: array + items: + $ref: "#/components/schemas/PostApproval" + - type: object + description: No Results + required: + - post_aprovals + properties: + post_approvals: + type: array + maxItems: 0 + # Post Events + /post_events.json: + get: + summary: Search Post Events + operationId: searchPostEvents + tags: + - Post Events + security: + - basicAuth: [] + parameters: + - $ref: "#/components/parameters/limit" + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/id" + - name: search[order] + in: query + schema: + type: string + enum: + - id_asc + - id_desc + - in: query + name: search[post_id] + schema: + type: number + - in: query + name: search[creator_id] + schema: + type: number + - in: query + name: search[creator_name] + schema: + type: string + - in: query + name: search[action] + schema: + type: string + enum: *post-event-actions + responses: + 200: + description: Success + content: + application/json: + schema: + type: object + required: + - post_events + properties: + post_events: + type: array + items: + $ref: "#/components/schemas/PostEvent" + # Post Flags + /post_flags.json: + get: + summary: Search Post Flags + operationId: searchPostFlags + tags: + - Post Flags + description: When no results are found, an object with an `post_flags` key is returned. + parameters: + - $ref: "#/components/parameters/limit" + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/id" + - $ref: "#/components/parameters/ip_addr" + - name: search[order] + in: query + schema: + type: string + enum: + - id_asc + - id_desc + - in: query + name: search[reason_matches] + schema: + type: string + - in: query + name: search[creator_id] + schema: + type: number + - in: query + name: search[creator_name] + schema: + type: string + - in: query + name: search[post_id] + schema: + type: number + - in: query + name: search[post_tags_match] + schema: + type: string + - in: query + name: search[type] + schema: + type: string + - in: query + name: search[is_resolved] + schema: + type: boolean + responses: + 200: + description: Success + content: + application/json: + schema: + anyOf: + - type: array + items: + $ref: "#/components/schemas/PostFlag" + - type: object + description: No Results + required: + - post_flags + properties: + post_flags: + type: array + maxItems: 0 + post: + summary: Create Post Flag + operationId: createPostFlag + tags: + - Post Flags + security: + - basicAuth: [] + description: Will error if post_id is not provided. + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + required: + - post_flag[post_id] + - post_flag[reason_name] + properties: + post_flag[post_id]: + type: number + post_flag[reason_name]: + type: string + enum: + - uploading_guidelines + - young_human + - dnp_artist + - pay_content + - trace + - previously_deleted + - real_porn + - corrupt + - inferior + post_flag[parent_id]: + type: number + responses: + 201: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/PostFlag" + 403: + $ref: "#/components/responses/AccessDenied" + 422: + $ref: "#/components/responses/ExpectedError" + /post_flags/{id}.json: + get: + summary: Get Post Flag + operationId: getPostFlag + tags: + - Post Flags + parameters: + - name: id + in: path + required: true + description: The ID of the post flag. + schema: + type: number + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/PostFlag" + 404: + $ref: "#/components/responses/NotFound" + # Post Replacements + /post_replacements.json: + get: + summary: Search Post Replacements + operationId: searchPostReplacements + tags: + - Post Replacements + description: When no results are found, an object with an `post_replacements` key is returned. + parameters: + - $ref: "#/components/parameters/limit" + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/id" + - in: query + name: search[file_ext] + schema: + type: string + - in: query + name: search[md5] + schema: + type: string + - in: query + name: search[status] + schema: + type: string + - in: query + name: search[creator_id] + schema: + type: number + - in: query + name: search[creator_name] + schema: + type: string + - in: query + name: search[approver_id] + schema: + type: number + - in: query + name: search[approver_name] + schema: + type: string + - in: query + name: search[rejector_id] + schema: + type: number + - in: query + name: search[rejector_name] + schema: + type: string + - in: query + name: search[uploader_name_on_approve] + schema: + type: string + - in: query + name: search[uploader_id_on_approve] + schema: + type: number + responses: + 200: + description: Success + content: + application/json: + schema: + anyOf: + - type: array + items: + $ref: "#/components/schemas/PostReplacement" + - type: object + description: No Results + required: + - post_replacements + properties: + post_replacements: + type: array + maxItems: 0 + post: + summary: Create Post Replacement + operationId: createPostReplacement + tags: + - Post Replacements + security: + - basicAuth: [] + requestBody: + content: + multipart/form-data: + schema: + type: object + required: + - post_replacement[reason] + properties: + post_replacement[replacement_file]: + type: string + format: binary + description: Mutually exclusive with replacement_url. + post_replacement[replacement_url]: + type: string + description: Mutually exclusive with replacement_file. + post_replacement[reason]: + type: string + post_replacement[source]: + type: string + post_replacement[as_pending]: + type: boolean + description: You must be Janitor+. + responses: + 200: + description: Success + content: + application/json: + schema: + type: object + required: + - success + - location + properties: + success: + type: boolean + enum: + - true + location: + type: string + example: "/posts/1" + 403: + $ref: "#/components/responses/AccessDenied" + 412: + $ref: "#/components/responses/MessageError" + 422: + $ref: "#/components/responses/ExpectedError" + /post_replacements/{id}.json: + delete: + summary: Delete Post Replacement + operationId: deletePostReplacement + tags: + - Post Replacements + description: You must be Admin+. + security: + - basicAuth: [] + parameters: + - name: id + in: path + required: true + description: The ID of the post replacement. + schema: + type: number + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + /post_replacements/{id}/approve.json: + put: + summary: Approve Post Replacement + operationId: approvePostReplacement + tags: + - Post Replacements + security: + - basicAuth: [] + description: You must have the "Approve Posts" permission. + parameters: + - name: id + in: path + required: true + description: The ID of the post replacement. + schema: + type: number + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + /post_replacements/{id}/reject.json: + put: + summary: Reject Post Replacement + operationId: rejectPostReplacement + tags: + - Post Replacements + security: + - basicAuth: [] + description: You must have the "Approve Posts" permission. + parameters: + - name: id + in: path + required: true + description: The ID of the post replacement. + schema: + type: number + requestBody: + content: + application/json: + schema: + type: object + properties: + reason: + type: string + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + /post_replacements/{id}/promote.json: + post: + summary: Promote Post Replacement + operationId: promotePostReplacement + tags: + - Post Replacements + security: + - basicAuth: [] + description: You must have the "Approve Posts" permission. + parameters: + - name: id + in: path + required: true + description: The ID of the post replacement. + schema: + type: number + responses: + 201: + description: Success + content: + application/json: + schema: + type: object + required: + - post + properties: + post: + $ref: "#/components/schemas/Post" + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + /post_replacements/{id}/toggle_penalize.json: + put: + summary: Toggle Post Replacement Penalty + operationId: togglePostReplacementPenalty + tags: + - Post Replacements + security: + - basicAuth: [] + description: You must have the "Approve Posts" permission. + parameters: + - name: id + in: path + required: true + description: The ID of the post replacement. + schema: + type: number + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + # Post Versions + /post_versions.json: + get: + summary: Search Post Versions + operationId: searchPostVersions + tags: + - Post Versions + description: Errors if no results are found. + parameters: + - $ref: "#/components/parameters/limit" + - name: page + in: query + description: The page number of results to get. Between 1 and 750. Note that for post versions specifically, you can only go through the 10,000 most recent results with page numbers. + schema: + type: number + minimum: 1 + maximum: 750 + - $ref: "#/components/parameters/id" + - in: query + name: search[updater_name] + schema: + type: string + - in: query + name: search[updater_id] + schema: + type: number + - in: query + name: search[post_id] + schema: + type: number + - in: query + name: search[start_id] + schema: + type: number + - in: query + name: search[rating] + schema: + type: string + enum: *ratings + - in: query + name: search[rating_changed] + schema: + type: string + enum: + - e + - q + - s + - any + - in: query + name: search[parent_id] + schema: + type: number + - in: query + name: search[parent_id_changed] + schema: + type: boolean + - in: query + name: search[tags] + schema: + type: string + - in: query + name: search[tags_removed] + schema: + type: string + - in: query + name: search[tags_added] + schema: + type: string + - in: query + name: search[locked_tags] + schema: + type: string + - in: query + name: search[locked_tags_removed] + schema: + type: string + - in: query + name: search[locked_tags_added] + schema: + type: string + - in: query + name: search[reason] + schema: + type: string + - in: query + name: search[description] + schema: + type: string + - in: query + name: search[description_changed] + schema: + type: boolean + - in: query + name: search[source_changed] + schema: + type: boolean + - in: query + name: search[uploads] + schema: + type: string + enum: + - included + - excluded + - only + responses: + 200: + description: Success + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/PostVersion" + # Post Votes + /posts/{id}/votes.json: + post: + summary: Create Post Vote + operationId: createPostVote + tags: + - Post Votes + security: + - basicAuth: [] + parameters: + - name: id + in: path + required: true + description: The ID of the post. + schema: + type: string + - name: score + in: query + required: true + schema: + type: number + enum: + - -1 + - 1 + - name: no_unvote + in: query + schema: + type: boolean + responses: + 200: + description: Success + content: + application/json: + schema: + type: object + required: + - score + - up + - down + - our_score + properties: + score: + type: number + up: + type: number + down: + type: number + our_score: + type: number + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + delete: + summary: Delete Post Vote + operationId: deletePostVote + tags: + - Post Votes + security: + - basicAuth: [] + parameters: + - name: id + in: path + required: true + description: The ID of the post. + schema: + type: string + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + /post_votes/lock.json: + post: + summary: Lock Post Vote + operationId: lockPostVote + tags: + - Post Votes + security: + - basicAuth: [] + description: You must be Moderator+. Errors if ids is not provided. + parameters: + - name: ids + in: query + required: true + description: The IDs of the post votes, comma separated. + schema: + type: string + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + /post_votes/delete.json: + post: + summary: Delete Post Vote + operationId: deletePostVotes + tags: + - Post Votes + security: + - basicAuth: [] + description: You must be Admin+. Errors if ids is not provided. + parameters: + - name: ids + in: query + required: true + description: The IDs of the post votes, comma separated. + schema: + type: string + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + # Tags + /tags.json: + get: + summary: Search Tags + operationId: searchTags + tags: + - Tags + description: When no results are found, an object with an `tags` key is returned. + parameters: + - $ref: "#/components/parameters/limit" + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/id" + - name: search[order] + in: query + schema: + type: string + enum: + - id_asc + - id_desc + - name + - date + - count + - similarity + - in: query + name: search[fuzzy_name_matches] + schema: + type: string + - in: query + name: search[name_matches] + schema: + type: string + - in: query + name: search[name] + schema: + type: string + - in: query + name: search[category] + schema: + type: number + description: Allows multiple, comma separated. + enum: *tag-categories + - in: query + name: search[hide_empty] + schema: + type: boolean + - in: query + name: search[has_wiki] + schema: + type: boolean + - in: query + name: search[has_artist] + schema: + type: boolean + responses: + 200: + description: Success + content: + application/json: + schema: + anyOf: + - type: array + items: + $ref: "#/components/schemas/Tag" + - type: object + description: No Results + required: + - tags + properties: + tags: + type: array + maxItems: 0 + /tags/{id}.json: + get: + summary: Get Tag + operationId: getTag + tags: + - Tags + parameters: + - name: id + in: path + required: true + description: The ID or name of the tag. + schema: + type: ["number", "string"] + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/Tag" + 404: + $ref: "#/components/responses/NotFound" + patch: + summary: Edit Tag + operationId: editTag + tags: + - Tags + security: + - basicAuth: [] + description: Must be Admin+ if the tag is locked or post count is >100. + parameters: + - name: id + in: path + required: true + description: The ID of the tag. + schema: + type: number + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + tag[category]: + type: number + enum: *tag-categories + tag[is_locked]: + description: Must be Admin+. + type: boolean + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + /tags/{id}/correction.json: + post: + summary: Correct Tag + operationId: correctTag + tags: + - Tags + security: + - basicAuth: [] + description: You must be Janitor+. `commit=Fix` must be set. + parameters: + - name: id + in: path + required: true + description: The ID of the tag. + schema: + type: number + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + required: + - commit + properties: + commit: + type: string + description: If not set, nothing will happen. + responses: + 302: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + /tags/preview.json: + get: + summary: Preview Tags + operationId: previewTags + tags: + - Tags + description: Note while this route does not require auth, without auth it requires a CSRF token. For that reason it has been marked as requiring auth. + security: + - basicAuth: [] + parameters: + - name: tags + in: query + required: true + description: The tags to preview, space separated. + schema: + type: string + responses: + 200: + description: Success + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/TagPreview" + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + # Tag Type Versions + /tag_type_versions.json: + get: + summary: Search Tag Versions + operationId: searchTagVersions + tags: + - Tag Versions + description: When no results are found, an object with an `tag_type_versions` key is returned. + parameters: + - $ref: "#/components/parameters/limit" + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/id" + - name: search[tag] + in: query + schema: + type: string + - name: search[user_id] + in: query + schema: + type: string + - name: search[user_name] + in: query + schema: + type: string + responses: + 200: + description: Success + content: + application/json: + schema: + anyOf: + - type: array + items: + $ref: "#/components/schemas/TagTypeVersion" + - type: object + description: No Results + required: + - tag_type_versions + properties: + tag_type_versions: + type: array + maxItems: 0 + # Tag Aliases + /tag_aliases.json: + get: + summary: Search Tag Aliases + operationId: searchTagAliases + tags: + - Tag Aliases + description: When no results are found, an object with an `tag_aliases` key is returned. + parameters: + - $ref: "#/components/parameters/limit" + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/id" + - name: search[order] + in: query + schema: + type: string + enum: + - id_asc + - id_desc + - created_at + - updated_at + - name + - tag_count + - in: query + name: search[name_matches] + schema: + type: string + - in: query + name: search[antecedent_name] + schema: + type: string + - in: query + name: search[consequent_name] + schema: + type: string + - in: query + name: search[status] + schema: + type: string + enum: *tag-request-statuses + - in: query + name: search[antecedent_tag_category] + schema: + type: number + enum: *tag-categories + - in: query + name: search[consequent_tag_category] + schema: + type: number + enum: *tag-categories + - in: query + name: search[creator_id] + schema: + type: number + - in: query + name: search[creator_name] + schema: + type: string + - in: query + name: search[approver_id] + schema: + type: number + - in: query + name: search[approver_name] + schema: + type: string + responses: + 200: + description: Success + content: + application/json: + schema: + anyOf: + - type: array + items: + $ref: "#/components/schemas/TagAlias" + - type: object + description: No Results + required: + - tag_aliases + properties: + tag_aliases: + type: array + maxItems: 0 + /tag_aliases/{id}.json: + get: + summary: Get Tag Alias + operationId: getTagAlias + tags: + - Tag Aliases + parameters: + - name: id + in: path + required: true + description: The ID of the tag alias. + schema: + type: number + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/TagAlias" + 404: + $ref: "#/components/responses/NotFound" + patch: + summary: Edit Tag Alias + operationId: editTagAlias + tags: + - Tag Aliases + security: + - basicAuth: [] + description: You must be Admin+. + parameters: + - name: id + in: path + required: true + description: The ID of the tag alias. + schema: + type: number + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + tag_alias[antecedent_name]: + type: string + tag_alias[consequent_name]: + type: string + tag_alias[forum_topic_id]: + type: string + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + delete: + summary: Reject Tag Alias + operationId: rejectTagAlias + tags: + - Tag Aliases + description: You must be the creator of the request (if pending), or Admin+. + security: + - basicAuth: [] + parameters: + - name: id + in: path + required: true + description: The ID of the tag alias. + schema: + type: number + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + /tag_aliases/{id}/approve.json: + post: + summary: Approve Tag Alias + operationId: approveTagAlias + tags: + - Tag Aliases + security: + - basicAuth: [] + description: You must be Admin+. + parameters: + - name: id + in: path + required: true + description: The ID of the tag alias. + schema: + type: number + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + /tag_alias_requests.json: + post: + summary: Create Tag Alias + operationId: createTagAlias + tags: + - Tag Aliases + security: + - basicAuth: [] + description: Errors will result in a 406 with no information. + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + required: + - tag_alias[antecedent_name] + - tag_alias[consequent_name] + - tag_alias[reason] + properties: + tag_alias[antecedent_name]: + type: string + tag_alias[consequent_name]: + type: string + tag_alias[reason]: + type: string + tag_alias[skip_forum]: + type: boolean + description: Must be Admin+. + responses: + 302: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 406: + description: Failure + # Tag Implications + /tag_implications.json: + get: + summary: Search Tag Implications + operationId: searchTagImplications + tags: + - Tag Implications + description: When no results are found, an object with an `tag_implications` key is returned. + parameters: + - $ref: "#/components/parameters/limit" + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/id" + - name: search[order] + in: query + schema: + type: string + enum: + - id_asc + - id_desc + - created_at + - updated_at + - name + - tag_count + - in: query + name: search[name_matches] + schema: + type: string + - in: query + name: search[antecedent_name] + schema: + type: string + - in: query + name: search[consequent_name] + schema: + type: string + - in: query + name: search[status] + schema: + type: string + enum: *tag-request-statuses + - in: query + name: search[antecedent_tag_category] + schema: + type: number + enum: *tag-categories + - in: query + name: search[consequent_tag_category] + schema: + type: number + enum: *tag-categories + - in: query + name: search[creator_id] + schema: + type: number + - in: query + name: search[creator_name] + schema: + type: string + - in: query + name: search[approver_id] + schema: + type: number + - in: query + name: search[approver_name] + schema: + type: string + responses: + 200: + description: Success + content: + application/json: + schema: + anyOf: + - type: array + items: + $ref: "#/components/schemas/TagImplication" + - type: object + description: No Results + required: + - tag_implications + properties: + tag_implications: + type: array + maxItems: 0 + /tag_implications/{id}.json: + get: + summary: Get Tag Implication + operationId: getTagImplication + tags: + - Tag Implications + parameters: + - name: id + in: path + required: true + description: The ID of the tag implication. + schema: + type: number + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/TagImplication" + 404: + $ref: "#/components/responses/NotFound" + patch: + summary: Edit Tag Implication + operationId: editTagImplication + tags: + - Tag Implications + security: + - basicAuth: [] + description: You must be Admin+. + parameters: + - name: id + in: path + required: true + description: The ID of the tag implication. + schema: + type: number + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + tag_implication[antecedent_name]: + type: string + tag_implication[consequent_name]: + type: string + tag_implication[forum_topic_id]: + type: string + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + delete: + summary: Reject Tag Implication + operationId: rejectTagImplication + tags: + - Tag Implications + description: You must be the creator of the request (if pending), or Admin+. + security: + - basicAuth: [] + parameters: + - name: id + in: path + required: true + description: The ID of the tag implication. + schema: + type: number + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + /tag_implications/{id}/approve.json: + post: + summary: Approve Tag Implication + operationId: approveTagImplication + tags: + - Tag Implications + security: + - basicAuth: [] + description: You must be Admin+. + parameters: + - name: id + in: path + required: true + description: The ID of the tag implication. + schema: + type: number + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + /tag_implication_requests.json: + post: + summary: Create Tag Implication + operationId: createTagImplication + tags: + - Tag Implications + security: + - basicAuth: [] + description: Errors will result in a 406 with no information. + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + required: + - tag_implication[antecedent_name] + - tag_implication[consequent_name] + - tag_implication[reason] + properties: + tag_implication[antecedent_name]: + type: string + tag_implication[consequent_name]: + type: string + tag_implication[reason]: + type: string + tag_implication[skip_forum]: + type: boolean + description: Must be Admin+. + responses: + 302: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 406: + description: Failure + # Bulk Related Tags + /related_tag/bulk.json: + post: + summary: List Bulk Related Tags + operationId: listBulkRelatedTags + tags: + - Related Tags + security: + - basicAuth: [] + requestBody: + content: + application/json: + schema: + type: object + properties: + query: + type: string + category_id: + type: number + enum: *tag-categories + responses: + 200: + description: Success + content: + application/json: + schema: + type: object + examples: + - {"male":[{"name":"male","count":0,"category_id":0}]} + patternProperties: + "^.+$": + type: array + items: + $ref: "#/components/schemas/BulkRelatedTag" + 403: + $ref: "#/components/responses/AccessDenied" + # Takedowns + /takedowns.json: + get: + summary: Search Takedowns + operationId: searchTakedowns + tags: + - Takedowns + description: When no results are found, an object with an `takedowns` key is returned. + parameters: + - $ref: "#/components/parameters/limit" + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/id" + - $ref: "#/components/parameters/ip_addr" + - in: query + name: search[order] + schema: + type: string + description: Must Admin+ to use. + enum: + - id_asc + - id_desc + - status + - post_count + - in: query + name: search[status] + schema: + type: string + - in: query + name: search[source] + description: Must be Moderator+ to use. + schema: + type: string + - in: query + name: search[reason] + description: Must be Moderator+ to use. + schema: + type: string + - in: query + name: search[creator_id] + description: Must be Moderator+ to use. + schema: + type: number + - in: query + name: search[creator_name] + description: Must be Moderator+ to use. + schema: + type: string + - in: query + name: search[reason_hidden] + description: Must be Moderator+ to use. + schema: + type: boolean + - in: query + name: search[instructions] + description: Must be Moderator+ to use. + schema: + type: string + - in: query + description: Must be Moderator+ to use. + name: search[post_id] + schema: + type: number + - in: query + description: Must be Moderator+ to use. + name: search[notes] + schema: + type: string + - in: query + description: Must be Admin+ to use. + name: search[email] + schema: + type: string + - in: query + name: search[vericode] + description: Must be Admin+ to use. + schema: + type: string + responses: + 200: + description: Success + content: + application/json: + schema: + anyOf: + - type: array + items: + $ref: "#/components/schemas/Takedown" + - type: object + description: No Results + required: + - takedowns + properties: + takedowns: + type: array + maxItems: 0 + post: + summary: Create Takedown + operationId: createTakedown + tags: + - Takedowns + security: + - basicAuth: [] + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + required: + - takedown[email] + - takedown[reason] + properties: + takedown[email]: + type: string + takedown[source]: + type: string + takedown[instructions]: + type: string + takedown[reason]: + type: string + takedown[post_ids]: + type: array + description: takedown[post_ids][]=1&takedown[post_ids][]=2 + items: + type: number + takedown[reason_hidden]: + type: boolean + takedown[notes]: + type: string + description: Must have the bd staff user flag to use. + takedown[del_post_ids]: + type: array + description: | + Must have the bd staff user flag to use. + takedown[del_post_ids][]=1&takedown[del_post_ids][]=2 + items: + type: number + takedown[status]: + type: string + description: Must have the bd staff user flag to use. + responses: + 201: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/Takedown" + 403: + $ref: "#/components/responses/AccessDenied" + 422: + $ref: "#/components/responses/ExpectedError" + /takedowns/{id}.json: + get: + summary: Get Takedown + operationId: getTakedown + tags: + - Takedowns + parameters: + - name: id + in: path + required: true + description: The ID of the takedown. + schema: + type: number + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/Takedown" + 404: + $ref: "#/components/responses/NotFound" + patch: + summary: Edit Takedown + operationId: editTakedown + tags: + - Takedowns + security: + - basicAuth: [] + description: You must have the bd staff user flag. + parameters: + - name: id + in: path + required: true + description: The ID of the takedown. + schema: + type: number + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + takedown[notes]: + type: string + takedown[reason_hidden]: + type: boolean + takedown_posts: + type: string + process_takedown: + type: boolean + description: If not truthy, the takedown will be denied. + delete_reason: + type: string + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + delete: + summary: Delete Takedown + operationId: deleteTakedown + tags: + - Takedowns + security: + - basicAuth: [] + description: You must have the bd staff user flag. + parameters: + - name: id + in: path + required: true + description: The ID of the takedown. + schema: + type: number + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + /takedowns/{id}/add_by_ids.json: + post: + summary: Add Posts To Takedown By IDs + operationId: addPostsToTakedownByIds + tags: + - Takedowns + security: + - basicAuth: [] + description: You must have the bd staff user flag. + parameters: + - name: id + in: path + required: true + description: The ID of the takedown. + schema: + type: number + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + required: + - post_ids + properties: + post_ids: + type: string + responses: + 200: + description: Success + content: + application/json: + schema: + type: object + required: + - added_count + - added_post_ids + properties: + added_count: + type: number + added_post_ids: + type: array + items: + type: number + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + /takedowns/{id}/add_by_tags.json: + post: + summary: Add Posts To Takedown By Tags + operationId: addPostsToTakedownByTags + tags: + - Takedowns + security: + - basicAuth: [] + description: You must have the bd staff user flag. + parameters: + - name: id + in: path + required: true + description: The ID of the takedown. + schema: + type: number + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + required: + - post_tags + properties: + post_tags: + type: string + responses: + 200: + description: Success + content: + application/json: + schema: + type: object + required: + - added_count + - added_post_ids + properties: + added_count: + type: number + added_post_ids: + type: array + items: + type: number + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + /takedowns/{id}/count_matching_posts.json: + post: + summary: Count Matching Posts + operationId: countMatchingPosts + tags: + - Takedowns + security: + - basicAuth: [] + description: You must have the bd staff user flag. + parameters: + - name: id + in: path + required: true + description: The ID of the takedown. + schema: + type: number + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + required: + - post_tags + properties: + post_tags: + type: string + responses: + 200: + description: Success + content: + application/json: + schema: + type: object + required: + - matched_post_count + properties: + matched_post_count: + type: number + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + /takedowns/{id}/remove_by_ids.json: + post: + summary: Remove Posts From Takedown By IDs + operationId: removePostsFromTakedownByIds + tags: + - Takedowns + security: + - basicAuth: [] + description: You must have the bd staff user flag. + parameters: + - name: id + in: path + required: true + description: The ID of the takedown. + schema: + type: number + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + required: + - post_ids + properties: + post_ids: + type: string + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + # Tickets + /tickets.json: + get: + summary: Search Tickets + operationId: searchTickets + tags: + - Tickets + description: You must be Janitor+ to see tickets you did not create. When no results are found, an object with an `tickets` key is returned. + parameters: + - $ref: "#/components/parameters/limit" + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/id" + - in: query + name: search[creator_name] + description: You must be Moderator+. + schema: + type: string + - in: query + name: search[creator_id] + description: You must be Moderator+ unless providing your own id. + schema: + type: number + - in: query + name: search[claimant_name] + description: You must be Moderator+. + schema: + type: string + - in: query + name: search[claimant_id] + description: You must be Moderator+. + schema: + type: number + - in: query + name: search[accused_name] + description: You must be Moderator+. + schema: + type: string + - in: query + name: search[accused_id] + description: You must be Moderator+. + schema: + type: number + - in: query + name: search[qtype] + schema: + type: string + enum: *ticket-types + - in: query + name: search[reason] + description: You must be Moderator+. + schema: + type: string + - in: query + name: search[status] + schema: + type: string + enum: + - pending + - partial + - approved + - pending_claimed + - pending_unclaimed + responses: + 200: + description: Success + content: + application/json: + schema: + anyOf: + - type: array + items: + $ref: "#/components/schemas/Ticket" + - type: object + description: No Results + required: + - tickets + properties: + tickets: + type: array + maxItems: 0 + 403: + $ref: "#/components/responses/AccessDenied" + /tickets/{id}.json: + get: + summary: Get Ticket + operationId: getTicket + tags: + - Tickets + security: + - basicAuth: [] + description: You must be Janitor+ to see tickets you did not create. + parameters: + - name: id + in: path + required: true + description: The ID of the ticket. + schema: + type: number + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/Ticket" + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + patch: + summary: Edit Ticket + operationId: editTicket + tags: + - Tickets + description: You must be Moderator+. + security: + - basicAuth: [] + parameters: + - name: id + in: path + required: true + description: The ID of the ticket. + schema: + type: number + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + required: + - ticket[response] + properties: + ticket[status]: + type: string + enum: + - partial + - approved + ticket[response]: + type: string + ticket[record_type]: + type: string + enum: *warning-types + ticket[send_update_dmail]: + type: boolean + description: An update dmail will always be sent when the status is changed. + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + /tickets/{id}/claim.json: + post: + summary: Claim Ticket + operationId: claimTicket + tags: + - Tickets + description: You must be Moderator+. Errors are quietly swallowed and shown as notices in html. + parameters: + - name: id + in: path + required: true + description: The ID of the ticket. + schema: + type: number + security: + - basicAuth: [] + responses: + 201: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/Ticket" + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + /tickets/{id}/unclaim.json: + post: + summary: Unclaim Ticket + operationId: unclaimTicket + tags: + - Tickets + description: You must be Moderator+. Errors are quietly swallowed and shown as notices in html. + parameters: + - name: id + in: path + required: true + description: The ID of the ticket. + schema: + type: number + security: + - basicAuth: [] + responses: + 201: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/Ticket" + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + # Uploads + /uploads.json: + get: + summary: Search Uploads + operationId: searchUploads + tags: + - Uploads + description: You must be Janitor+. When no results are found, an object with an `uploads` key is returned. + parameters: + - $ref: "#/components/parameters/limit" + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/id" + - name: search[order] + in: query + schema: + type: string + enum: + - id_asc + - id_desc + - in: query + name: search[uploader_id] + schema: + type: number + - in: query + name: search[uploader_name] + schema: + type: string + - in: query + name: search[source] + schema: + type: string + - in: query + name: search[source_matches] + schema: + type: string + - in: query + name: search[rating] + schema: + type: string + enum: *ratings + - in: query + name: search[parent_id] + schema: + type: number + - in: query + name: search[post_id] + schema: + type: number + - in: query + name: search[has_post] + schema: + type: boolean + - in: query + name: search[post_tags_match] + schema: + type: string + - in: query + name: search[status] + schema: + type: string + description: | + Note: The "error" status will be proceeded by an error, ex: "error: RuntimeError - No file or source URL provided" + enum: + - completed + - processing + - pending + - error + - in: query + name: search[backtrace] + schema: + type: string + - in: query + name: search[tag_string] + schema: + type: string + responses: + 200: + description: Success + content: + application/json: + schema: + anyOf: + - type: array + items: + $ref: "#/components/schemas/Upload" + - type: object + description: No Results + required: + - uploads + properties: + uploads: + type: array + maxItems: 0 + post: + summary: Upload Post + operationId: uploadPost + tags: + - Uploads + - Posts + security: + - basicAuth: [] + requestBody: + content: + multipart/form-data: + schema: + type: object + required: + - upload[tag_string] + - upload[rating] + properties: + upload[file]: + type: string + format: binary + description: Mutually exclusive with direct_url. + upload[direct_url]: + type: string + description: Mutually exclusive with file. + upload[source]: + type: string + upload[tag_string]: + type: string + upload[rating]: + type: string + enum: *ratings + upload[parent_id]: + type: number + upload[description]: + type: string + upload[as_pending]: + type: boolean + description: Must have the "Unrestricted Uploads" permission. + upload[locked_rating]: + type: boolean + description: Must be Privileged+ to use. + upload[locked_tags]: + type: string + description: Must be Admin+ to use. + responses: + 200: + description: Success + content: + application/json: + schema: + type: object + required: + - success + - location + - post_id + properties: + success: + type: boolean + enum: + - true + location: + type: string + post_id: + type: number + 403: + $ref: "#/components/responses/AccessDenied" + 412: + $ref: "#/components/responses/MessageError" + # Upload Whitelists + /upload_whitelists.json: + get: + summary: Search Upload Whitelists + operationId: searchUploadWhitelists + tags: + - Upload Whitelists + description: When no results are found, an object with an `upload_whitelists` key is returned. + parameters: + - $ref: "#/components/parameters/limit" + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/id" + - name: search[order] + in: query + schema: + type: string + enum: + - id_asc + - id_desc + - pattern + - updated_at + - created_at + - in: query + name: search[pattern] + schema: + type: string + - in: query + name: search[note] + schema: + type: string + responses: + 200: + description: Success + content: + application/json: + schema: + anyOf: + - type: array + items: + $ref: "#/components/schemas/UploadWhitelist" + - type: object + description: No Results + required: + - upload_whitelists + properties: + upload_whitelists: + type: array + maxItems: 0 + post: + summary: Create Upload Whitelist + operationId: createUploadWhitelist + tags: + - Upload Whitelists + security: + - basicAuth: [] + description: You must be Admin+. + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + required: + - upload_whitelist[allowed] + - upload_whitelist[pattern] + properties: + upload_whitelist[allowed]: + type: string + upload_whitelist[pattern]: + type: string + upload_whitelist[reason]: + type: string + upload_whitelist[note]: + type: string + upload_whitelist[hidden]: + type: boolean + /upload_whitelists/{id}.json: + patch: + summary: Edit Upload Whitelist + operationId: editUploadWhitelist + tags: + - Upload Whitelists + security: + - basicAuth: [] + parameters: + - name: id + in: path + required: true + description: The ID of the upload whitelist entry. + schema: + type: number + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + upload_whitelist[allowed]: + type: string + upload_whitelist[pattern]: + type: string + upload_whitelist[reason]: + type: string + upload_whitelist[note]: + type: string + upload_whitelist[hidden]: + type: boolean + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + delete: + summary: Delete Upload Whitelist + operationId: deleteUploadWhitelist + tags: + - Upload Whitelists + security: + - basicAuth: [] + description: You must be Admin+. + parameters: + - name: id + in: path + required: true + description: The ID of the upload whitelist. + schema: + type: number + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + /upload_whitelists/{id}/is_allowed.json: + get: + summary: Check If URL Is Allowed + operationId: checkIfUrlIsAllowed + tags: + - Upload Whitelists + security: + - basicAuth: [] + parameters: + - name: id + in: path + required: true + description: The ID of the upload whitelist. + schema: + type: number + - name: url + in: query + schema: + type: string + responses: + 200: + description: Success + content: + application/json: + schema: + type: object + required: + - url + - domain + - is_allowed + - reason + properties: + url: + type: string + domain: + type: string + is_allowed: + type: boolean + reason: + type: string + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + # Users + /users.json: + get: + summary: Search Users + operationId: searchUsers + tags: + - Users + parameters: + - $ref: "#/components/parameters/limit" + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/id" + - $ref: "#/components/parameters/ip_addr" + - name: search[order] + in: query + schema: + type: string + enum: + - id_asc + - id_desc + - name + - post_upload_count + - note_count + - post_update_count + - in: query + name: search[name_matches] + schema: + type: string + - in: query + name: search[about_me] + schema: + type: string + - in: query + name: search[avatar_id] + schema: + type: number + - in: query + name: search[level] + schema: + type: number + - in: query + name: search[min_level] + schema: + type: number + - in: query + name: search[max_level] + schema: + type: number + - in: query + name: search[can_upload_free] + schema: + type: boolean + - in: query + name: search[can_approve_posts] + schema: + type: boolean + - in: query + name: search[email_matches] + description: You must be Admin+. + schema: + type: string + responses: + 200: + description: Success + content: + application/json: + schema: + type: array + items: + anyOf: + - $ref: "#/components/schemas/User" + - $ref: "#/components/schemas/FullUser" + description: Extra properties included for the current user. + /users/{id}.json: + get: + summary: Get User + operationId: getUser + tags: + - Users + parameters: + - name: id + in: path + required: true + description: The ID of the user. + schema: + type: number + responses: + 200: + description: Success + content: + application/json: + schema: + anyOf: + - $ref: "#/components/schemas/User" + - $ref: "#/components/schemas/FullCurrentUser" + description: Extra properties included for the current user. + 404: + $ref: "#/components/responses/NotFound" + patch: + summary: Edit Current User + operationId: editCurrentUser + tags: + - Users + parameters: + - name: id + in: path + required: true + description: The ID of the user. The actual value is ignored, but something must be supplied. + schema: + type: number + security: + - basicAuth: [] + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + user[comment_threshold]: + type: number + user[default_image_size]: + type: string + enum: + - large + - fit + - fitv + - original + user[favorite_tags]: + type: string + user[blacklisted_tags]: + type: string + user[time_zone]: + type: string + description: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones + user[per_page]: + type: number + user[custom_style]: + type: string + user[description_collapsed_initially]: + type: boolean + user[hide_comments]: + type: boolean + user[receive_email_notifications]: + type: boolean + user[enable_keyboard_navigation]: + type: boolean + user[enable_privacy_mode]: + type: boolean + user[disable_user_dmails]: + type: boolean + user[blacklist_users]: + type: boolean + user[show_post_statistics]: + type: boolean + user[style_usernames]: + type: boolean + user[show_hidden_comments]: + type: boolean + user[enable_autocomplete]: + type: boolean + user[disable_cropped_thumbnails]: + type: boolean + user[enable_safe_mode]: + type: boolean + user[disable_responsive_mode]: + type: boolean + user[dmail_filter_attributes][id]: + type: number + user[dmail_filter_attributes][words]: + type: string + user[profile_about]: + type: string + user[profile_artinfo]: + type: string + user[avatar_id]: + type: number + user[enable_compact_uploader]: + type: boolean + description: You must have uploaded at least 10 posts. + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 422: + $ref: "#/components/responses/ExpectedError" + /users/upload_limit.json: + get: + summary: Get Current User + operationId: getCurrentUser + tags: + - Users + security: + - basicAuth: [] + description: | + This is a crude but effective way to get the currently authenticated user without scraping HTML. + Note that this route does not include some properties included in the show action. + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/CurrentUser" + 403: + $ref: "#/components/responses/AccessDenied" + /maintenance/user/count_fixes.json: + post: + summary: Fix User Counts + operationId: fixUserCounts + tags: + - Users + security: + - basicAuth: [] + responses: + 302: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + /maintenance/user/dmail_filter.json: + patch: + summary: Update User Dmail Filter + operationId: updateUserDmailFilter + tags: + - DMails + parameters: + - name: dmail_id + in: query + required: true + description: Due to the odd way this route works, a dmail is REQUIRED to edit your dmail filter. You must be the owner of the dmail. + schema: + type: number + security: + - basicAuth: [] + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + required: + - dmail_filter[words] + properties: + dmail_filter[words]: + type: string + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + # User Feedbacks + /user_feedbacks.json: + get: + summary: Search User Feedbacks + operationId: searchUserFeedbacks + tags: + - User Feedbacks + description: When no results are found, an object with an `user_feedbacks` key is returned. + parameters: + - $ref: "#/components/parameters/limit" + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/id" + - name: search[order] + in: query + schema: + type: string + enum: + - id_asc + - id_desc + - in: query + name: search[deleted] + description: You must be Moderator+. + schema: + type: string + enum: + - included + - excluded + - only + - in: query + name: search[body_matches] + schema: + type: string + - in: query + name: search[user_id] + schema: + type: number + - in: query + name: search[user_name] + schema: + type: string + - in: query + name: search[creator_id] + schema: + type: number + - in: query + name: search[creator_name] + schema: + type: string + - in: query + name: search[category] + schema: + type: string + enum: *feedback-categories + responses: + 200: + description: Success + content: + application/json: + schema: + anyOf: + - type: array + items: + $ref: "#/components/schemas/UserFeedback" + - type: object + description: No Results + required: + - user_feedbacks + properties: + user_feedbacks: + type: array + maxItems: 0 + post: + summary: Create User Feedback + operationId: createUserFeedback + tags: + - User Feedbacks + security: + - basicAuth: [] + description: You must be Moderator+. + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + required: + - user_feedback[body] + - user_feedback[category] + properties: + user_feedback[user_id]: + type: number + user_feedback[user_name]: + type: string + user_feedback[body]: + type: string + user_feedback[category]: + type: string + enum: *feedback-categories + responses: + 201: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/UserFeedback" + 403: + $ref: "#/components/responses/AccessDenied" + 422: + $ref: "#/components/responses/ExpectedError" + /user_feedbacks/{id}.json: + get: + summary: Get User Feedback + operationId: getUserFeedback + tags: + - User Feedbacks + description: You must be Moderator+ if the feedback is deleted. + parameters: + - name: id + in: path + required: true + description: The ID of the feedback. + schema: + type: number + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/UserFeedback" + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + patch: + summary: Edit User Feedback + operationId: editUserFeedback + tags: + - User Feedbacks + security: + - basicAuth: [] + description: You must be Moderator+. + parameters: + - name: id + in: path + required: true + description: The ID of the feedback. + schema: + type: number + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + user_feedback[body]: + type: string + user_feedback[category]: + type: string + enum: *feedback-categories + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + delete: + summary: Destroy User Feedback + operationId: destroyUserFeedback + tags: + - User Feedbacks + security: + - basicAuth: [] + description: You must be Admin+, or the creator and Moderator+. + parameters: + - name: id + in: path + required: true + description: The ID of the feedback. + schema: + type: number + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + /user_feedbacks/{id}/delete.json: + put: + summary: Delete User Feedback + operationId: deleteUserFeedback + tags: + - User Feedbacks + security: + - basicAuth: [] + description: You must be Moderator+. + parameters: + - name: id + in: path + required: true + description: The ID of the feedback. + schema: + type: number + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + /user_feedbacks/{id}/undelete.json: + put: + summary: Undelete User Feedback + operationId: undeleteUserFeedback + tags: + - User Feedbacks + security: + - basicAuth: [] + description: You must be Moderator+. + parameters: + - name: id + in: path + required: true + description: The ID of the feedback. + schema: + type: number + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + # User Name Change Requests + /user_name_change_requests.json: + get: + summary: Search User Name Change Requests + operationId: searchUserNameChangeRequests + tags: + - User Name Change Requests + security: + - basicAuth: [] + description: You must be Moderator+. When no results are found, an object with an `user_name_change_requests` key is returned. + parameters: + - $ref: "#/components/parameters/limit" + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/id" + - name: search[order] + in: query + schema: + type: string + enum: + - id_asc + - id_desc + - in: query + name: search[current_id] + schema: + type: number + - in: query + name: search[current_name] + schema: + type: string + - in: query + name: search[original_name] + schema: + type: string + - in: query + name: search[desired_name] + schema: + type: string + responses: + 200: + description: Success + content: + application/json: + schema: + anyOf: + - type: array + items: + $ref: "#/components/schemas/UserNameChangeRequest" + - type: object + description: No Results + required: + - user_name_change_requests + properties: + user_name_change_requests: + type: array + maxItems: 0 + post: + summary: Create User Name Change Request + operationId: createUserNameChangeRequest + tags: + - User Name Change Requests + security: + - basicAuth: [] + description: You must be Moderator+. + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + required: + - user_name_change_request[desired_name] + properties: + user_name_change_request[desired_name]: + type: string + user_name_change_request[change_reason]: + type: string + responses: + 302: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 422: + $ref: "#/components/responses/ExpectedError" + /user_name_change_requests/{id}.json: + get: + summary: Get User Name Change Request + operationId: getUserNameChangeRequest + tags: + - User Name Change Requests + security: + - basicAuth: [] + description: You must be the creator of the request or Moderator+. + parameters: + - name: id + in: path + required: true + description: The ID of the name change request. + schema: + type: number + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/UserNameChangeRequest" + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + # Wiki Pages + /wiki_pages.json: + get: + summary: Search Wiki Pages + operationId: searchWikiPages + tags: + - Wiki Pages + parameters: + - $ref: "#/components/parameters/limit" + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/id" + - name: search[order] + in: query + schema: + type: string + enum: + - id_asc + - id_desc + - title + - post_count + - in: query + name: search[title] + schema: + type: string + - in: query + name: search[title_matches] + schema: + type: string + - in: query + name: search[body_matches] + schema: + type: string + - in: query + name: search[other_names_match] + schema: + type: string + - in: query + name: search[creator_id] + schema: + type: number + - in: query + name: search[creator_name] + schema: + type: string + - in: query + name: search[parent] + schema: + type: string + - in: query + name: search[other_names_present] + schema: + type: boolean + - in: query + name: search[is_locked] + schema: + type: boolean + - in: query + name: search[is_deleted] + schema: + type: boolean + responses: + 200: + description: Success + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/WikiPage" + post: + summary: Create Wiki Page + operationId: createWikiPage + tags: + - Wiki Pages + security: + - basicAuth: [] + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + required: + - wiki_page[title] + - wiki_page[body] + properties: + wiki_page[title]: + type: string + wiki_page[body]: + type: string + wiki_page[edit_reason]: + type: string + wiki_page[parent]: + type: string + description: Must be Privileged+ to use. + wiki_page[is_locked]: + type: boolean + description: Must be Janitor+ to use. + wiki_page[is_deleted]: + type: boolean + description: Must be Janitor+ to use. + wiki_page[skip_secondary_validations]: + type: boolean + description: Must be Janitor+ to use. + responses: + 201: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/WikiPage" + 403: + $ref: "#/components/responses/AccessDenied" + 422: + $ref: "#/components/responses/ExpectedError" + /wiki_pages/{id}.json: + get: + summary: Get Wiki Page + operationId: getWikiPage + tags: + - Wiki Pages + parameters: + - name: id + in: path + required: true + description: The ID or name of the wiki page. + schema: + type: number + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/WikiPage" + 404: + $ref: "#/components/responses/NotFound" + patch: + summary: Edit Wiki Page + operationId: editWikiPage + tags: + - Wiki Pages + security: + - basicAuth: [] + description: You must be Janitor+ if the wiki page is locked. + parameters: + - name: id + in: path + required: true + description: The ID of the wiki page. + schema: + type: number + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + wiki_page[body]: + type: string + wiki_page[edit_reason]: + type: string + wiki_page[parent]: + type: string + description: Must be Privileged+ to use. + wiki_page[title]: + type: string + description: Must be Janitor+ to use. + wiki_page[is_locked]: + type: boolean + description: Must be Janitor+ to use. + wiki_page[is_deleted]: + type: boolean + description: Must be Janitor+ to use. + wiki_page[skip_secondary_validations]: + type: boolean + description: Must be Janitor+ to use. + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + delete: + summary: Delete Wiki Page + operationId: deleteWikiPage + tags: + - Wiki Pages + security: + - basicAuth: [] + description: You must be Admin+ + parameters: + - name: id + in: path + required: true + description: The ID of the wiki page. + schema: + type: number + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + /wiki_page/{id}/revert.json: + put: + summary: Revert Wiki Page + operationId: revertWikiPage + tags: + - Wiki Pages + security: + - basicAuth: [] + parameters: + - name: id + in: path + required: true + description: The ID of the wiki page. + schema: + type: number + - name: version_id + in: query + required: true + description: The version ID to revert to. + schema: + type: number + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + # Wiki Page Versions + /wiki_page_versions.json: + get: + summary: Search Wiki Page Versions + operationId: searchWikiPageVersions + tags: + - Wiki Page Versions + description: When no results are found, an object with an `wiki_page_versions` key is returned. + parameters: + - $ref: "#/components/parameters/limit" + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/id" + - $ref: "#/components/parameters/ip_addr" + - name: search[order] + in: query + schema: + type: string + enum: + - id_asc + - id_desc + - in: query + name: search[updater_id] + schema: + type: number + - in: query + name: search[updater_name] + schema: + type: string + - in: query + name: search[wiki_page_id] + schema: + type: number + - in: query + name: search[title] + schema: + type: string + - in: query + name: search[body] + schema: + type: string + - in: query + name: search[is_locked] + schema: + type: boolean + - in: query + name: search[is_deleted] + schema: + type: boolean + responses: + 200: + description: Success + content: + application/json: + schema: + anyOf: + - type: array + items: + $ref: "#/components/schemas/WikiPageVersion" + - type: object + description: No Results + required: + - wiki_page_versions + properties: + wiki_page_versions: + type: array + maxItems: 0 + /wiki_page_versions/{id}.json: + get: + summary: Get Wiki Page Version + operationId: getWikiPageVersion + tags: + - Wiki Page Versions + parameters: + - name: id + in: path + required: true + description: The ID of the wiki page version. + schema: + type: number + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/WikiPageVersion" + 404: + $ref: "#/components/responses/NotFound" + # Admin Users + /admin/users/alt_list.json: + get: + summary: Get Alt List + operationId: getAltList + tags: + - Admin Users + security: + - basicAuth: [] + description: You must be Admin+. + parameters: + - name: page + in: query + description: The page number of results to get. Between 1 and 9999. + schema: + type: number + minimum: 1 + maximum: 9999 + responses: + 200: + description: Success + content: + application/json: + schema: + type: array + items: + oneOf: + - type: number + description: ID of concerned user. + - type: array + items: + type: number + description: ID of suspected alt. + examples: + - [[1,[2]],[2,[1]]] + /admins/users/{id}.json: + patch: + summary: Admin Edit User + operationId: adminEditUser + tags: + - Admin Users + security: + - basicAuth: [] + description: You must be Admin+. If editing an Admin+, you must be Owner+. + parameters: + - name: id + in: path + required: true + description: The ID of the user. + schema: + type: number + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + user[verified]: + type: boolean + description: Must have the bd staff user flag to use. + user[level]: + type: number + description: Must have the bd staff user flag to promote to Admin+. + user[name]: + type: string + user[profile_about]: + type: string + user[profile_artinfo]: + type: string + user[base_upload_limit]: + type: number + user[enable_privacy_mode]: + type: boolean + user[email]: + type: string + description: Must have the bd staff user flag to use. + user[can_approve_posts]: + type: boolean + user[can_upload_free]: + type: boolean + user[no_flagging]: + type: boolean + user[replacements_beta]: + type: boolean + responses: + 204: + description: Success + 400: + $ref: "#/components/responses/MessageError" + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + # Moderator Posts + /moderator/post/posts/{id}/delete.json: + post: + summary: Delete Post + operationId: deletePost + tags: + - Posts + security: + - basicAuth: [] + description: You must have the "Approve Posts" permission. `commit=Delete` must be set. + parameters: + - name: id + in: path + required: true + description: The ID of the post. + schema: + type: number + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + reason: + type: string + description: If the post does not have an active flag, this is required. + move_favorites: + type: boolean + description: Move favorites to parent. + copy_sources: + type: boolean + description: Copy sources to parent. + copy_tags: + type: boolean + description: Copy tags to parent. + commit: + type: string + description: If not set, nothing will happen. + enum: + - Delete + + responses: + 302: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + /moderator/post/posts/{id}/undelete.json: + post: + summary: Undelete Post + operationId: undeletePost + tags: + - Posts + security: + - basicAuth: [] + description: You must have the "Approve Posts" permission. + parameters: + - name: id + in: path + required: true + description: The ID of the post. + schema: + type: number + responses: + 201: + description: Success + content: + application/json: + schema: + type: object + required: + - post + properties: + post: + $ref: "#/components/schemas/Post" + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + /moderator/post/posts/{id}/regenerate_thumbnails.json: + post: + summary: Regenerate Post Thumbnails + operationId: regeneratePostThumbnails + tags: + - Posts + security: + - basicAuth: [] + description: You must be Janitor+. + parameters: + - name: id + in: path + required: true + description: The ID of the post. + schema: + type: number + responses: + 201: + description: Success + content: + application/json: + schema: + type: object + required: + - post + properties: + post: + $ref: "#/components/schemas/Post" + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + /moderator/post/posts/{id}/regenerate_videos.json: + post: + summary: Regenerate Post Videos + operationId: regeneratePostVideos + tags: + - Posts + security: + - basicAuth: [] + description: You must be Janitor+. + parameters: + - name: id + in: path + required: true + description: The ID of the post. + schema: + type: number + responses: + 204: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + /moderator/post/posts/{id}/expunge.json: + post: + summary: Expunge Post + operationId: expungePost + tags: + - Posts + security: + - basicAuth: [] + description: You must have the "Approve Posts" permission and be Admin+. + parameters: + - name: id + in: path + required: true + description: The ID of the post. + schema: + type: number + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + reason: + type: string + responses: + 201: + description: Success + content: + application/json: + schema: + type: object + required: + - post + properties: + post: + $ref: "#/components/schemas/Post" + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + 422: + $ref: "#/components/responses/ExpectedError" + /moderator/post/posts/{id}/move_favorites.json: + post: + summary: Move Post Favorites + operationId: movePostFavorites + tags: + - Posts + security: + - basicAuth: [] + description: You must have the "Approve Posts" permission. `commit=Submit`` must be set. + parameters: + - name: id + in: path + required: true + description: The ID of the post. + schema: + type: number + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + required: + - commit + properties: + commit: + type: string + description: If not set, nothing will happen. + enum: + - Submit + responses: + 302: + description: Success + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + /moderator/post/approval.json: + post: + summary: Approve Post + operationId: approvePost + tags: + - Posts + - Post Approvals + security: + - basicAuth: [] + description: You must have the "Approve Posts" permission. + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + required: + - post_id + properties: + post_id: + type: number + responses: + 201: + description: Success + 204: + description: Failure + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + delete: + summary: Unapprove Post + operationId: unapprovePost + tags: + - Posts + - Post Approvals + security: + - basicAuth: [] + description: You must have the "Approve Posts" permission. The response does not differ for success or failure. + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + required: + - post_id + properties: + post_id: + type: number + responses: + 204: + description: Success/Failure + 403: + $ref: "#/components/responses/AccessDenied" + 404: + $ref: "#/components/responses/NotFound" + # Post Disapprovals + /moderator/post/disapprovals.json: + get: + summary: Search Post Disapprovals + operationId: searchPostDisapprovals + tags: + - Post Disapprovals + security: + - basicAuth: [] + description: You must have the "Approve Posts" permission When no results are found, an object with a `post_disapprovals` key is returned. + parameters: + - $ref: "#/components/parameters/limit" + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/id" + - name: search[order] + in: query + schema: + type: string + enum: + - id_asc + - id_desc + - post_id + - post_id_desc + - in: query + name: search[creator_id] + schema: + type: number + - in: query + name: search[creator_name] + schema: + type: string + - in: query + name: search[post_id] + schema: + type: number + - in: query + name: search[message] + schema: + type: string + - in: query + name: search[post_tags_match] + schema: + type: string + - in: query + name: search[reason] + schema: + type: string + - in: query + name: search[has_message] + schema: + type: boolean + responses: + 200: + description: Success + content: + application/json: + schema: + anyOf: + - type: array + items: + $ref: "#/components/schemas/PostDisapproval" + - type: object + description: No Results + required: + - post_disapprovals + properties: + post_disapprovals: + type: array + maxItems: 0 + post: + summary: Create Post Disapproval + operationId: createPostDisapproval + tags: + - Post Disapprovals + security: + - basicAuth: [] + description: You must have the "Approve Posts" permission. + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + required: + - post_disapproval[post_id] + - post_disapproval[reason] + properties: + post_disapproval[post_id]: + type: number + post_disapproval[reason]: + type: string + enum: + - borderline_quality + - borderline_relevancy + - other + post_disapproval[message]: + type: string + responses: + 201: + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/PostDisapproval" + 403: + $ref: "#/components/responses/AccessDenied" + 422: + $ref: "#/components/responses/ExpectedError" diff --git a/src/s3.go b/src/s3.go index 7ed18ec..2ffa8a7 100644 --- a/src/s3.go +++ b/src/s3.go @@ -49,6 +49,7 @@ func NewS3Service(ctx context.Context, region, endpoint, accessKey, secretKey, b uploader := manager.NewUploader(s3Client, func(u *manager.Uploader) { u.PartSize = 10 * 1024 * 1024 // 10MB Parts + u.Concurrency = 999999 // give me all the power! (i mean, threads) }) downloader := manager.NewDownloader(s3Client, func(d *manager.Downloader) { diff --git a/update_openapi.sh b/update_openapi.sh new file mode 100644 index 0000000..cd9d704 --- /dev/null +++ b/update_openapi.sh @@ -0,0 +1,5 @@ +echo "Deleting old routes" +rm ./src/openapi/e621.yaml + +echo "Updating e621 routes" +curl -o ./src/openapi/e621.yaml https://raw.githubusercontent.com/DonovanDMC/E621OpenAPI/master/openapi.yaml