From 2effd589af73cccf6261e8d2a6400a4c42490e0c Mon Sep 17 00:00:00 2001 From: SoXX Date: Fri, 19 Jul 2024 21:40:18 +0200 Subject: [PATCH] migration from old git (no git history) --- .editorconfig | 16 + .gitignore | 194 + .../go_test_e621_sdk_go__endpoints_.xml | 12 + README.md | 67 + example/highlevel/favorites.go | 53 + example/highlevel/post.go | 45 + example/highlevel/user.go | 23 + example/lowlevel/dmail.go | 65 + example/lowlevel/favorite.go | 60 + example/lowlevel/note.go | 57 + example/lowlevel/pool.go | 57 + example/lowlevel/post.go | 57 + example/lowlevel/tag.go | 97 + example/lowlevel/user.go | 57 + example/midlevel/dmail.go | 43 + example/midlevel/favorite.go | 49 + example/midlevel/note.go | 58 + example/midlevel/pool.go | 57 + example/midlevel/post.go | 68 + example/midlevel/tag.go | 81 + example/midlevel/user.go | 54 + go.mod | 11 + go.sum | 12 + main.go | 4 + pkg/e621/builder/dmail.go | 79 + pkg/e621/builder/favorite.go | 66 + pkg/e621/builder/favorite_test.go | 48 + pkg/e621/builder/note.go | 47 + pkg/e621/builder/note_test.go | 49 + pkg/e621/builder/notes.go | 98 + pkg/e621/builder/notes_test.go | 49 + pkg/e621/builder/pool.go | 47 + pkg/e621/builder/pool_test.go | 49 + pkg/e621/builder/pools.go | 114 + pkg/e621/builder/pools_test.go | 48 + pkg/e621/builder/post.go | 47 + pkg/e621/builder/post_test.go | 54 + pkg/e621/builder/posts.go | 85 + pkg/e621/builder/posts_test.go | 49 + pkg/e621/builder/tag.go | 47 + pkg/e621/builder/tag_test.go | 49 + pkg/e621/builder/tags.go | 103 + pkg/e621/builder/user.go | 46 + pkg/e621/builder/user_test.go | 49 + pkg/e621/builder/users.go | 127 + pkg/e621/builder/users_test.go | 49 + pkg/e621/client.go | 244 + pkg/e621/endpoints/dbexport.go | 117 + pkg/e621/endpoints/dmail.go | 234 + pkg/e621/endpoints/favorite.go | 64 + pkg/e621/endpoints/favorite_test.go | 48 + pkg/e621/endpoints/note.go | 121 + pkg/e621/endpoints/note_test.go | 87 + pkg/e621/endpoints/pool.go | 114 + pkg/e621/endpoints/pool_test.go | 87 + pkg/e621/endpoints/post.go | 113 + pkg/e621/endpoints/post_test.go | 92 + pkg/e621/endpoints/tag.go | 125 + pkg/e621/endpoints/tag_test.go | 48 + pkg/e621/endpoints/user.go | 114 + pkg/e621/endpoints/user_test.go | 87 + pkg/e621/model/basic.go | 15 + pkg/e621/model/dmail.go | 16 + pkg/e621/model/note.go | 17 + pkg/e621/model/pool.go | 30 + pkg/e621/model/post.go | 93 + pkg/e621/model/tag.go | 26 + pkg/e621/model/user.go | 51 + pkg/e621/utils/constants.go | 4 + pkg/e621/utils/error.go | 106 + pkg/e621/utils/test.go | 28 + tests/note.json | 15 + tests/notes.json | 1127 +++ tests/pool.json | 38 + tests/pools.json | 4002 ++++++++ tests/post.json | 111 + tests/posts.json | 8820 +++++++++++++++++ tests/tag.json | 11 + tests/tags.json | 827 ++ tests/user.json | 26 + tests/users.json | 1127 +++ 81 files changed, 20881 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 .idea/runConfigurations/go_test_e621_sdk_go__endpoints_.xml create mode 100644 README.md create mode 100644 example/highlevel/favorites.go create mode 100644 example/highlevel/post.go create mode 100644 example/highlevel/user.go create mode 100644 example/lowlevel/dmail.go create mode 100644 example/lowlevel/favorite.go create mode 100644 example/lowlevel/note.go create mode 100644 example/lowlevel/pool.go create mode 100644 example/lowlevel/post.go create mode 100644 example/lowlevel/tag.go create mode 100644 example/lowlevel/user.go create mode 100644 example/midlevel/dmail.go create mode 100644 example/midlevel/favorite.go create mode 100644 example/midlevel/note.go create mode 100644 example/midlevel/pool.go create mode 100644 example/midlevel/post.go create mode 100644 example/midlevel/tag.go create mode 100644 example/midlevel/user.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go create mode 100644 pkg/e621/builder/dmail.go create mode 100644 pkg/e621/builder/favorite.go create mode 100644 pkg/e621/builder/favorite_test.go create mode 100644 pkg/e621/builder/note.go create mode 100644 pkg/e621/builder/note_test.go create mode 100644 pkg/e621/builder/notes.go create mode 100644 pkg/e621/builder/notes_test.go create mode 100644 pkg/e621/builder/pool.go create mode 100644 pkg/e621/builder/pool_test.go create mode 100644 pkg/e621/builder/pools.go create mode 100644 pkg/e621/builder/pools_test.go create mode 100644 pkg/e621/builder/post.go create mode 100644 pkg/e621/builder/post_test.go create mode 100644 pkg/e621/builder/posts.go create mode 100644 pkg/e621/builder/posts_test.go create mode 100644 pkg/e621/builder/tag.go create mode 100644 pkg/e621/builder/tag_test.go create mode 100644 pkg/e621/builder/tags.go create mode 100644 pkg/e621/builder/user.go create mode 100644 pkg/e621/builder/user_test.go create mode 100644 pkg/e621/builder/users.go create mode 100644 pkg/e621/builder/users_test.go create mode 100644 pkg/e621/client.go create mode 100644 pkg/e621/endpoints/dbexport.go create mode 100644 pkg/e621/endpoints/dmail.go create mode 100644 pkg/e621/endpoints/favorite.go create mode 100644 pkg/e621/endpoints/favorite_test.go create mode 100644 pkg/e621/endpoints/note.go create mode 100644 pkg/e621/endpoints/note_test.go create mode 100644 pkg/e621/endpoints/pool.go create mode 100644 pkg/e621/endpoints/pool_test.go create mode 100644 pkg/e621/endpoints/post.go create mode 100644 pkg/e621/endpoints/post_test.go create mode 100644 pkg/e621/endpoints/tag.go create mode 100644 pkg/e621/endpoints/tag_test.go create mode 100644 pkg/e621/endpoints/user.go create mode 100644 pkg/e621/endpoints/user_test.go create mode 100644 pkg/e621/model/basic.go create mode 100644 pkg/e621/model/dmail.go create mode 100644 pkg/e621/model/note.go create mode 100644 pkg/e621/model/pool.go create mode 100644 pkg/e621/model/post.go create mode 100644 pkg/e621/model/tag.go create mode 100644 pkg/e621/model/user.go create mode 100644 pkg/e621/utils/constants.go create mode 100644 pkg/e621/utils/error.go create mode 100644 pkg/e621/utils/test.go create mode 100644 tests/note.json create mode 100644 tests/notes.json create mode 100644 tests/pool.json create mode 100644 tests/pools.json create mode 100644 tests/post.json create mode 100644 tests/posts.json create mode 100644 tests/tag.json create mode 100644 tests/tags.json create mode 100644 tests/user.json create mode 100644 tests/users.json diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..f523b09 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +root = true + +[*] +charset = utf-8 +end_of_line = crlf +indent_size = 4 +indent_style = space +insert_final_newline = false +max_line_length = 120 +tab_width = 4 + +[{*.go,*.go2}] +indent_style = tab + +[{*.yaml,*.yml}] +indent_size = 2 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3d25383 --- /dev/null +++ b/.gitignore @@ -0,0 +1,194 @@ +# Created by https://www.toptal.com/developers/gitignore/api/windows,linux,goland+all,macos,go +# Edit at https://www.toptal.com/developers/gitignore?templates=windows,linux,goland+all,macos,go + +.env + +### Go ### +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work + +### GoLand+all ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### GoLand+all Patch ### +# Ignore everything but code style settings and run configurations +# that are supposed to be shared within teams. + +.idea/* + +!.idea/codeStyles +!.idea/runConfigurations + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/windows,linux,goland+all,macos,go +/.scannerwork/ + +*.env \ No newline at end of file diff --git a/.idea/runConfigurations/go_test_e621_sdk_go__endpoints_.xml b/.idea/runConfigurations/go_test_e621_sdk_go__endpoints_.xml new file mode 100644 index 0000000..6dcfffa --- /dev/null +++ b/.idea/runConfigurations/go_test_e621_sdk_go__endpoints_.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..2b6ca1e --- /dev/null +++ b/README.md @@ -0,0 +1,67 @@ +# Go-e621 SDK + +An unofficial e621 SDK (Client) library to interact with **e621**, **e923** and **e6ai**. Maintained by the Anthrove Development Team. + +- Caching? +- Auto pagination + - sync + - async +- connected/inelegant calls +- more docs + +# Completeness + +_Legend_ + +| Symbol | Meaning | +|:------------------:|:----------------------| +| :x: | Not implemented | +| :heavy_minus_sign: | Partially implemented | +| :heavy_check_mark: | Fully implemented | + +_High Level API_ + +| Area | Complete | Comment | +|:-----------------|:------------------:|:--------| +| Authentication | :x: | | +| Posts | :heavy_minus_sign: | | +| Tags | :heavy_minus_sign: | | +| Tag aliases | :x: | | +| Tag implications | :x: | | +| Notes | :x: | | +| Pools | :x: | | +| Users | :heavy_check_mark: | | +| Favorites | :heavy_minus_sign: | | +| DB export | :x: | | + +_Mid Level API_ + +| Area | Get | Set | +|:-----------------|:------------------:|:----| +| Authentication | :x: | | +| Posts | :heavy_check_mark: | | +| Tags | :heavy_check_mark: | | +| Tag aliases | :x: | | +| Tag implications | :x: | | +| Notes | :heavy_check_mark: | | +| Pools | :heavy_check_mark: | | +| Users | :heavy_check_mark: | | +| Favorites | :heavy_check_mark: | | +| DB export | :x: | +| DMails | :heavy_minus_sign: | | + +_Low Level API_ + +| Area | Get | Set | +|:-----------------|:------------------:|:----| +| Authentication | :x: | | +| Posts | :heavy_check_mark: | | +| Tags | :heavy_check_mark: | | +| Tag aliases | :x: | | +| Tag implications | :x: | | +| Notes | :heavy_check_mark: | | +| Pools | :heavy_check_mark: | | +| Users | :heavy_check_mark: | | +| Favorites | :heavy_check_mark: | | +| DB export | :x: | | +| DMails | :heavy_check_mark: | | \ No newline at end of file diff --git a/example/highlevel/favorites.go b/example/highlevel/favorites.go new file mode 100644 index 0000000..f41037b --- /dev/null +++ b/example/highlevel/favorites.go @@ -0,0 +1,53 @@ +package main + +import ( + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621" + _ "github.com/joho/godotenv/autoload" + "log" + "os" +) + +func main() { + client := e621.NewClient(os.Getenv("API_USER"), os.Getenv("API_KEY")) + { + favorites, err := client.GetFavoritesForUser("selloo") + if err != nil { + log.Panic(err) + } + posts, err := favorites.Execute() + if err != nil { + log.Panic(err) + } + log.Printf("URL of favorite post 0 is: %s", posts[0].File.URL) + } + + { + favoritesBuilder, _ := client.GetFavoritesForUser("selloo") + favorites, err := client.GetNFavoritesForUser(10, favoritesBuilder) + if err != nil { + log.Panic(err) + } + + log.Println(len(favorites)) + + } + + { + favoritesBuilder, _ := client.GetFavoritesForUser("selloo") + favorites, err := client.GetAllFavoritesForUser(favoritesBuilder) + if err != nil { + log.Panic(err) + } + log.Println(len(favorites)) + } + + { + favoritesWithTags := client.GetFavoritesForUserWithTags("selloo", "fennec male solo") + posts, err := favoritesWithTags.Execute() + if err != nil { + log.Panic(err) + } + log.Printf("URL of favorite post 0 with tags is: %s", posts[0].File.URL) + } + +} diff --git a/example/highlevel/post.go b/example/highlevel/post.go new file mode 100644 index 0000000..bf79a1e --- /dev/null +++ b/example/highlevel/post.go @@ -0,0 +1,45 @@ +package main + +import ( + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621" + _ "github.com/joho/godotenv/autoload" + "log" + "os" +) + +func main() { + client := e621.NewClient(os.Getenv("API_USER"), os.Getenv("API_KEY")) + + posts, err := client.GetPosts().Execute() + if err != nil { + log.Panic(err) + } + log.Printf("Post 0 has following tags: %s", posts[0].Tags) + + postWithTags, err := client.GetPosts().Tags("fennec male solo order:score").Execute() + if err != nil { + log.Panic(err) + } + log.Printf("Post 0 with tags has following ID: %d", postWithTags[0].ID) + + post, err := client.GetPostByID(1337).Execute() + if err != nil { + log.Panic(err) + } + log.Printf("Post 1337 has following tags: %s", post.Tags) + + postBuilder := client.GetPosts() + posts1, err := client.GetNPosts(600, postBuilder) + if err != nil { + log.Panic(err) + } + log.Println(len(posts1)) + + postBuilder = client.GetPosts().Tags("how_to_dragon_your_train") + posts, err = client.GetAllPosts(postBuilder) + if err != nil { + log.Panic(err) + } + log.Println(len(posts)) + +} diff --git a/example/highlevel/user.go b/example/highlevel/user.go new file mode 100644 index 0000000..d2527c0 --- /dev/null +++ b/example/highlevel/user.go @@ -0,0 +1,23 @@ +package main + +import ( + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621" + _ "github.com/joho/godotenv/autoload" + "log" + "os" +) + +func main() { + client := e621.NewClient(os.Getenv("API_USER"), os.Getenv("API_KEY")) + user, err := client.GetUserByName("selloo").Execute() + if err != nil { + log.Panic(err) + } + log.Printf("User ID of user %s: %d ", user.Name, user.ID) + + user, err = client.GetUserByID(1337).Execute() + if err != nil { + log.Panic(err) + } + log.Printf("User Name of user with ID %d: %s ", user.ID, user.Name) +} diff --git a/example/lowlevel/dmail.go b/example/lowlevel/dmail.go new file mode 100644 index 0000000..4e50b2d --- /dev/null +++ b/example/lowlevel/dmail.go @@ -0,0 +1,65 @@ +package main + +import ( + "log" + "net/http" + "os" + + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/endpoints" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model" + _ "github.com/joho/godotenv/autoload" +) + +func main() { + requestContext := model.RequestContext{ + Client: http.Client{}, + Host: "https://e621.net", + UserAgent: "Go-e621-SDK (@username)", + Username: os.Getenv("API_USER"), + APIKey: os.Getenv("API_KEY"), + } + + log.Println("Getting a DMail by ID:") + dmail, err := endpoints.GetDmail(requestContext, 1) + if err != nil { + log.Println(err) + } else { + log.Println(dmail.Body) + } + log.Println("----------") + + log.Println("Deleting a DMail by ID:") + err = endpoints.DeleteDmail(requestContext, 1) + if err != nil { + log.Println(err) + } + log.Println("----------") + + log.Println("Marking a DMail as read by ID:") + err = endpoints.MarkAsReadDmail(requestContext, 1) + if err != nil { + log.Println(err) + } + log.Println("----------") + + log.Println("Getting all DMails:") + query := map[string]string{ + "limit": "5", + } + dmails, err := endpoints.GetAllDmails(requestContext, query) + if err != nil { + log.Println(err) + } else { + for _, dmail := range dmails { + log.Println(dmail.Body) + } + } + log.Println("----------") + + log.Println("Marking all DMails as read:") + err = endpoints.MarkAsReadAllDmails(requestContext) + if err != nil { + log.Println(err) + } + log.Println("----------") +} diff --git a/example/lowlevel/favorite.go b/example/lowlevel/favorite.go new file mode 100644 index 0000000..6a37b1a --- /dev/null +++ b/example/lowlevel/favorite.go @@ -0,0 +1,60 @@ +package main + +import ( + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/endpoints" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model" + _ "github.com/joho/godotenv/autoload" + "log" + "net/http" + "os" +) + +func main() { + // Define the request context with essential information. + requestContext := model.RequestContext{ + Client: http.Client{}, + Host: "https://e621.net", + UserAgent: "Go-e621-SDK (@username)", + Username: os.Getenv("API_USER"), // Replace with your username + APIKey: os.Getenv("API_KEY"), // Replace with your API key + } + + // Log: Getting favorites from the API. + log.Println("Getting favorites API user: ") + + // Define query parameters for retrieving favorites. + query := map[string]string{ + "limit": "5", + } + + // Call the GetFavorites function to retrieve favorite posts. + posts, err := endpoints.GetFavorites(requestContext, query) + + if err != nil { + log.Println(err) + } else { + // Log the URL of the first favorite post. + log.Println(posts[0].File.URL) + } + log.Println("----------") + + // Log: Getting favorites for a specific user. + log.Println("Getting favorites for user: ") + + // Update the query parameters to include a specific user's ID. + query = map[string]string{ + "user_id": "25483", // Replace with the desired user's ID + "limit": "5", + } + + // Call the GetFavorites function to retrieve favorite posts for the specified user. + posts, err = endpoints.GetFavorites(requestContext, query) + + if err != nil { + log.Println(err) + } else { + // Log the URL of the first favorite post. + log.Println(posts[0].File.URL) + } + log.Println("----------") +} diff --git a/example/lowlevel/note.go b/example/lowlevel/note.go new file mode 100644 index 0000000..6f1f2bb --- /dev/null +++ b/example/lowlevel/note.go @@ -0,0 +1,57 @@ +package main + +import ( + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/endpoints" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model" + _ "github.com/joho/godotenv/autoload" + "log" + "net/http" + "os" +) + +func main() { + // Define the request context with essential information. + requestContext := model.RequestContext{ + Client: http.Client{}, + Host: "https://e621.net", + UserAgent: "Go-e621-SDK (@username)", + Username: os.Getenv("API_USER"), // Replace with your username + APIKey: os.Getenv("API_KEY"), // Replace with your API key + } + + // Log: Getting a single note. + log.Println("Getting single note: ") + + // Specify the note ID for retrieval. + noteID := "36957" // Replace with the desired note's ID. + + // Call the GetNote function to retrieve the specified note. + note, err := endpoints.GetNote(requestContext, noteID) + + if err != nil { + log.Println(err) + } else { + // Log the body of the retrieved note. + log.Println(note.Body) + } + log.Println("----------") + + // Log: Getting a list of notes. + log.Println("Getting a list of notes: ") + + // Define query parameters for retrieving a list of notes. + query := map[string]string{ + "limit": "5", + } + + // Call the GetNotes function to retrieve a list of notes based on the query parameters. + notes, err := endpoints.GetNotes(requestContext, query) + + if err != nil { + log.Println(err) + } else { + // Log the number of notes retrieved. + log.Println(len(notes)) + } + log.Println("----------") +} diff --git a/example/lowlevel/pool.go b/example/lowlevel/pool.go new file mode 100644 index 0000000..291d984 --- /dev/null +++ b/example/lowlevel/pool.go @@ -0,0 +1,57 @@ +package main + +import ( + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/endpoints" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model" + _ "github.com/joho/godotenv/autoload" + "log" + "net/http" + "os" +) + +func main() { + // Define the request context with essential information. + requestContext := model.RequestContext{ + Client: http.Client{}, + Host: "https://e621.net", + UserAgent: "Go-e621-SDK (@username)", + Username: os.Getenv("API_USER"), // Replace with your username + APIKey: os.Getenv("API_KEY"), // Replace with your API key + } + + // Log: Getting a single pool. + log.Println("Getting single pool: ") + + // Specify the pool ID for retrieval. + poolID := "36957" // Replace with the desired pool's ID. + + // Call the GetPool function to retrieve the specified pool. + pool, err := endpoints.GetPool(requestContext, poolID) + + if err != nil { + log.Println(err) + } else { + // Log the name of the retrieved pool. + log.Println(pool.Name) + } + log.Println("----------") + + // Log: Getting a list of pools. + log.Println("Getting a list of pools: ") + + // Define query parameters for retrieving a list of pools. + query := map[string]string{ + "limit": "5", + } + + // Call the GetPools function to retrieve a list of pools based on the query parameters. + pools, err := endpoints.GetPools(requestContext, query) + + if err != nil { + log.Println(err) + } else { + // Log the number of pools retrieved. + log.Println(len(pools)) + } + log.Println("----------") +} diff --git a/example/lowlevel/post.go b/example/lowlevel/post.go new file mode 100644 index 0000000..3f6bff7 --- /dev/null +++ b/example/lowlevel/post.go @@ -0,0 +1,57 @@ +package main + +import ( + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/endpoints" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model" + _ "github.com/joho/godotenv/autoload" + "log" + "net/http" + "os" +) + +func main() { + // Define the request context with essential information. + requestContext := model.RequestContext{ + Client: http.Client{}, + Host: "https://e621.net", + UserAgent: "Go-e621-SDK (@username)", + Username: os.Getenv("API_USER"), // Replace with your username + APIKey: os.Getenv("API_KEY"), // Replace with your API key + } + + // Log: Getting a single post. + log.Println("Getting single post: ") + + // Specify the post ID for retrieval. + postID := "4353480" // Replace with the desired post's ID. + + // Call the GetPost function to retrieve the specified post. + post, err := endpoints.GetPost(requestContext, postID) + + if err != nil { + log.Println(err) + } else { + // Log the URL of the retrieved post. + log.Println(post.File.URL) + } + log.Println("----------") + + // Log: Getting a list of posts. + log.Println("Getting a list of posts: ") + + // Define query parameters for retrieving a list of posts. + query := map[string]string{ + "limit": "5", + } + + // Call the GetPosts function to retrieve a list of posts based on the query parameters. + posts, err := endpoints.GetPosts(requestContext, query) + + if err != nil { + log.Println(err) + } else { + // Log the number of posts retrieved. + log.Println(len(posts)) + } + log.Println("----------") +} diff --git a/example/lowlevel/tag.go b/example/lowlevel/tag.go new file mode 100644 index 0000000..fcac66b --- /dev/null +++ b/example/lowlevel/tag.go @@ -0,0 +1,97 @@ +package main + +import ( + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/endpoints" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model" + _ "github.com/joho/godotenv/autoload" + "log" + "net/http" + "os" +) + +func main() { + // Define the request context with essential information. + requestContext := model.RequestContext{ + Client: http.Client{}, + Host: "https://e621.net", + UserAgent: "Go-e621-SDK (@username)", + Username: os.Getenv("API_USER"), // Replace with your username + APIKey: os.Getenv("API_KEY"), // Replace with your API key + } + + // Log: Getting a single tag. + log.Println("Getting a single tag: ") + + // Specify the tag ID for retrieval. + tagID := "1530" // Replace with the desired tag's ID. + + // Call the GetTag function to retrieve the specified tag. + tag, err := endpoints.GetTag(requestContext, tagID) + + if err != nil { + log.Println(err) + } else { + // Log the name of the retrieved tag. + log.Println(tag.Name) + } + log.Println("----------") + + // Log: Getting a list of tags. + log.Println("Getting a list of tags: ") + + // Define query parameters for retrieving a list of tags. + query := map[string]string{ + "limit": "5", + } + + // Call the GetTags function to retrieve a list of tags based on the query parameters. + tagList, err := endpoints.GetTags(requestContext, query) + + if err != nil { + log.Println(err) + } else { + // Log the number of tags retrieved. + log.Println(len(tagList)) + } + log.Println("----------") + + // Log: Searching for tags containing "cat." + log.Println("Searching for tags containing 'cat': ") + + // Define query parameters for searching tags that match "cat." + query = map[string]string{ + "limit": "5", + "search[name_matches]": "cat*", + } + + // Call the GetTags function with the search query to retrieve matching tags. + tagList, err = endpoints.GetTags(requestContext, query) + + if err != nil { + log.Println(err) + } else { + // Log the retrieved tags. + log.Println(tagList) + } + log.Println("----------") + + // Log: Searching for tags with the category "artist." + log.Println("Searching for tags with the category 'artist': ") + + // Define query parameters for searching tags in the "artist" category. + query = map[string]string{ + "limit": "5", + "search[category]": "1", + } + + // Call the GetTags function with the category filter to retrieve artist tags. + tagList, err = endpoints.GetTags(requestContext, query) + + if err != nil { + log.Println(err) + } else { + // Log the retrieved artist tags. + log.Println(tagList) + } + log.Println("----------") +} diff --git a/example/lowlevel/user.go b/example/lowlevel/user.go new file mode 100644 index 0000000..ed19bbb --- /dev/null +++ b/example/lowlevel/user.go @@ -0,0 +1,57 @@ +package main + +import ( + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/endpoints" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model" + _ "github.com/joho/godotenv/autoload" + "log" + "net/http" + "os" +) + +func main() { + // Define the request context with essential information. + requestContext := model.RequestContext{ + Client: http.Client{}, + Host: "https://e621.net", + UserAgent: "Go-e621-SDK (@username)", + Username: os.Getenv("API_USER"), // Replace with your username + APIKey: os.Getenv("API_KEY"), // Replace with your API key + } + + // Log: Getting a single user. + log.Println("Getting a single user: ") + + // Specify the username for retrieval. + username := "selloo" // Replace with the desired username. + + // Call the GetUser function to retrieve the specified user. + user, err := endpoints.GetUser(requestContext, username) + + if err != nil { + log.Println(err) + } else { + // Log the ID of the retrieved user. + log.Println(user.ID) + } + log.Println("----------") + + // Log: Getting a list of users. + log.Println("Getting a list of users: ") + + // Define query parameters for retrieving a list of users. + query := map[string]string{ + "limit": "5", + } + + // Call the GetUsers function to retrieve a list of users based on the query parameters. + userList, err := endpoints.GetUsers(requestContext, query) + + if err != nil { + log.Println(err) + } else { + // Log the number of users retrieved. + log.Println(len(userList)) + } + log.Println("----------") +} diff --git a/example/midlevel/dmail.go b/example/midlevel/dmail.go new file mode 100644 index 0000000..35f3d36 --- /dev/null +++ b/example/midlevel/dmail.go @@ -0,0 +1,43 @@ +package main + +import ( + "log" + "net/http" + "os" + + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/builder" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model" + _ "github.com/joho/godotenv/autoload" +) + +func main() { + requestContext := model.RequestContext{ + Client: http.Client{}, + Host: "https://e621.net", + UserAgent: "Go-e621-SDK (@username)", + Username: os.Getenv("API_USER"), + APIKey: os.Getenv("API_KEY"), + } + + log.Println("Getting DMails API user: ") + + getDMails := builder.NewGetDMailsBuilder(requestContext) + dMails, err := getDMails.SetLimit(5).Execute() + + if err != nil { + log.Println(err) + } else { + log.Println(dMails[0].Title) + } + log.Println("----------") + + log.Println("Getting DMails for user: ") + + dMails, err = getDMails.SetLimit(5).ByToName("specificUser").Execute() + if err != nil { + log.Println(err) + } else { + log.Println(dMails[0].Title) + } + log.Println("----------") +} diff --git a/example/midlevel/favorite.go b/example/midlevel/favorite.go new file mode 100644 index 0000000..17d1fa3 --- /dev/null +++ b/example/midlevel/favorite.go @@ -0,0 +1,49 @@ +package main + +import ( + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/builder" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model" + _ "github.com/joho/godotenv/autoload" + "log" + "net/http" + "os" +) + +func main() { + // Define the request context with essential information. + requestContext := model.RequestContext{ + Client: http.Client{}, + Host: "https://e621.net", + UserAgent: "Go-e621-SDK (@username)", + Username: os.Getenv("API_USER"), // Replace with your username + APIKey: os.Getenv("API_KEY"), // Replace with your API key + } + + // Log: Getting favorites from the API. + log.Println("Getting favorites API user: ") + + // Call the GetFavorites function to retrieve favorite posts. + getFavorites := builder.NewGetFavoritesBuilder(requestContext) + posts, err := getFavorites.SetLimit(5).Execute() + + if err != nil { + log.Println(err) + } else { + // Log the URL of the first favorite post. + log.Println(posts[0].File.URL) + } + log.Println("----------") + + // Log: Getting favorites for a specific user. + log.Println("Getting favorites for user: ") + + // Call the GetFavorites function to retrieve favorite posts for the specified user. + posts, err = getFavorites.SetLimit(5).SetUserID(1337).Execute() + if err != nil { + log.Println(err) + } else { + // Log the URL of the first favorite post. + log.Println(posts[0].File.URL) + } + log.Println("----------") +} diff --git a/example/midlevel/note.go b/example/midlevel/note.go new file mode 100644 index 0000000..a9fae34 --- /dev/null +++ b/example/midlevel/note.go @@ -0,0 +1,58 @@ +package main + +import ( + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/builder" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model" + _ "github.com/joho/godotenv/autoload" + "log" + "net/http" + "os" + "strconv" +) + +func main() { + // Define the request context with essential information. + requestContext := model.RequestContext{ + Client: http.Client{}, + Host: "https://e621.net", + UserAgent: "Go-e621-SDK (@username)", + Username: os.Getenv("API_USER"), // Replace with your username + APIKey: os.Getenv("API_KEY"), // Replace with your API key + } + + // Log: Getting a single note. + log.Println("Getting single note: ") + + // Specify the note ID for retrieval. + noteID := 36957 // Replace with the desired note's ID. + + // Call the GetNote function to retrieve the specified note. + getNote := builder.NewGetNoteBuilder(requestContext) + note, err := getNote.SetNoteID(noteID).Execute() + + if err != nil { + log.Println(err) + } else { + // Log the body of the retrieved note. + log.Println(note.Body) + } + log.Println("----------") + + // Log: Getting a list of notes. + log.Println("Getting a list of notes: ") + + // Call the GetNotes function to retrieve a list of notes based on the query parameters. + getNotes := builder.NewGetNotesBuilder(requestContext) + notes, err := getNotes.SetLimit(5).Active(true).SearchInBody("furry").Execute() + + if err != nil { + log.Println(err) + } else { + // Log the number of notes retrieved. + log.Println(len(notes)) + for _, note := range notes { + log.Printf("Note by %s - %s : %s", note.CreatorName, note.Body, strconv.FormatInt(note.ID, 10)) + } + } + log.Println("----------") +} diff --git a/example/midlevel/pool.go b/example/midlevel/pool.go new file mode 100644 index 0000000..d07345b --- /dev/null +++ b/example/midlevel/pool.go @@ -0,0 +1,57 @@ +package main + +import ( + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/builder" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model" + _ "github.com/joho/godotenv/autoload" + "log" + "net/http" + "os" +) + +func main() { + // Define the request context with essential information. + requestContext := model.RequestContext{ + Client: http.Client{}, + Host: "https://e621.net", + UserAgent: "Go-e621-SDK (@username)", + Username: os.Getenv("API_USER"), // Replace with your username + APIKey: os.Getenv("API_KEY"), // Replace with your API key + } + + // Log: Getting a single pool. + log.Println("Getting single pool: ") + + // Specify the pool ID for retrieval. + poolID := 36957 // Replace with the desired pool's ID. + + // Call the GetPool function to retrieve the specified pool. + getPool := builder.NewGetPoolBuilder(requestContext) + pool, err := getPool.ID(poolID).Execute() + + if err != nil { + log.Println(err) + } else { + // Log the name of the retrieved pool. + log.Println(pool.Name) + } + log.Println("----------") + + // Log: Getting a list of pools. + log.Println("Getting a list of pools: ") + + // Call the GetPools function to retrieve a list of pools based on the query parameters. + getPools := builder.NewGetPoolsBuilder(requestContext) + pools, err := getPools.SetLimit(5).Active(true).SearchDescription("mass effect").Execute() + + if err != nil { + log.Println(err) + } else { + // Log the number of pools retrieved. + log.Println(len(pools)) + log.Println(pools[1].Name) + log.Println(pools[1].ID) + } + log.Println("----------") + +} diff --git a/example/midlevel/post.go b/example/midlevel/post.go new file mode 100644 index 0000000..db2fc43 --- /dev/null +++ b/example/midlevel/post.go @@ -0,0 +1,68 @@ +package main + +import ( + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/builder" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model" + _ "github.com/joho/godotenv/autoload" + "log" + "net/http" + "os" +) + +func main() { + // Define the request context with essential information. + requestContext := model.RequestContext{ + Client: http.Client{}, + Host: "https://e621.net", + UserAgent: "Go-e621-SDK (@username)", + Username: os.Getenv("API_USER"), // Replace with your username + APIKey: os.Getenv("API_KEY"), // Replace with your API key + } + + // Log: Getting a single post. + log.Println("Getting single post: ") + + // Specify the post ID for retrieval. + var postID model.PostID = 4353480 // Replace with the desired post's ID. + + // Call the GetPost function to retrieve the specified post. + getPost := builder.NewGetPostBuilder(requestContext) + post, err := getPost.SetPostID(postID).Execute() + + if err != nil { + log.Println(err) + } else { + // Log the URL of the retrieved post. + log.Println(post.File.URL) + } + log.Println("----------") + + // Log: Getting a list of posts. + log.Println("Getting a list of posts: ") + + // Call the GetPosts function to retrieve a list of posts based on the query parameters. + getPosts := builder.NewGetPostsBuilder(requestContext) + posts, err := getPosts.SetLimit(5).Execute() + + if err != nil { + log.Println(err) + } else { + // Log the number of posts retrieved. + log.Println(len(posts)) + } + log.Println("----------") + + // Log: Getting a list of posts with tags. + log.Println("Getting a list of posts: ") + + // Call the GetPosts function to retrieve a list of posts based on the query parameters. + posts, err = getPosts.SetLimit(5).Tags("fennec").Execute() + + if err != nil { + log.Println(err) + } else { + // Log the number of posts retrieved. + log.Println(len(posts)) + } + log.Println("----------") +} diff --git a/example/midlevel/tag.go b/example/midlevel/tag.go new file mode 100644 index 0000000..8afc3f1 --- /dev/null +++ b/example/midlevel/tag.go @@ -0,0 +1,81 @@ +package main + +import ( + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/builder" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model" + _ "github.com/joho/godotenv/autoload" + "log" + "net/http" + "os" +) + +func main() { + // Define the request context with essential information. + requestContext := model.RequestContext{ + Client: http.Client{}, + Host: "https://e621.net", + UserAgent: "Go-e621-SDK (@username)", + Username: os.Getenv("API_USER"), // Replace with your username + APIKey: os.Getenv("API_KEY"), // Replace with your API key + } + + // Log: Getting a single tag. + log.Println("Getting a single tag: ") + + // Specify the tag ID for retrieval. + tagID := 1530 // Replace with the desired tag's ID. + + // Call the GetTag function to retrieve the specified tag. + getTag := builder.NewGetTagBuilder(requestContext) + tag, err := getTag.SetTagID(tagID).Execute() + + if err != nil { + log.Println(err) + } else { + // Log the name of the retrieved tag. + log.Println(tag.Name) + } + log.Println("----------") + + // Log: Getting a list of tags. + log.Println("Getting a list of tags: ") + + // Call the GetTags function to retrieve a list of tags based on the query parameters. + getTags := builder.NewGetTagsBuilder(requestContext) + tagList, err := getTags.SetLimit(5).Artist(false).Execute() + + if err != nil { + log.Println(err) + } else { + // Log the number of tags retrieved. + log.Println(len(tagList)) + } + log.Println("----------") + + // Log: Searching for tags containing "cat." + log.Println("Searching for tags containing 'cat': ") + + // Call the GetTags function with the search query to retrieve matching tags. + tagList, err = getTags.SetLimit(5).SearchName("cat*").Execute() + if err != nil { + log.Println(err) + } else { + // Log the retrieved tags. + log.Println(tagList) + } + log.Println("----------") + + // Log: Searching for tags with the category "artist." + log.Println("Searching for tags with the category 'artist': ") + + // Call the GetTags function with the category filter to retrieve artist tags. + tagList, err = getTags.SetLimit(5).SearchCategory(model.Artist).Execute() + + if err != nil { + log.Println(err) + } else { + // Log the retrieved artist tags. + log.Println(tagList) + } + log.Println("----------") +} diff --git a/example/midlevel/user.go b/example/midlevel/user.go new file mode 100644 index 0000000..9528218 --- /dev/null +++ b/example/midlevel/user.go @@ -0,0 +1,54 @@ +package main + +import ( + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/builder" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model" + _ "github.com/joho/godotenv/autoload" + "log" + "net/http" + "os" +) + +func main() { + // Define the request context with essential information. + requestContext := model.RequestContext{ + Client: http.Client{}, + Host: "https://e621.net", + UserAgent: "Go-e621-SDK (@username)", + Username: os.Getenv("API_USER"), // Replace with your username + APIKey: os.Getenv("API_KEY"), // Replace with your API key + } + + // Log: Getting a single user. + log.Println("Getting a single user: ") + + // Specify the username for retrieval. + username := "selloo" // Replace with the desired username. + + // Call the GetUser function to retrieve the specified user. + userBuilder := builder.NewGetUserBuilder(requestContext) + user, err := userBuilder.SetUsername(username).Execute() + + if err != nil { + log.Println(err) + } else { + // Log the ID of the retrieved user. + log.Println(user.ID) + } + log.Println("----------") + + // Log: Getting a list of users. + log.Println("Getting a list of users: ") + + // Call the GetUsers function to retrieve a list of users based on the query parameters. + usersBuilder := builder.NewGetUsersBuilder(requestContext) + userList, err := usersBuilder.SetLimit(5).Execute() + + if err != nil { + log.Println(err) + } else { + // Log the number of users retrieved. + log.Println(len(userList)) + } + log.Println("----------") +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..e006cb7 --- /dev/null +++ b/go.mod @@ -0,0 +1,11 @@ +module git.dragse.it/anthrove/e621-sdk-go + +go 1.21.3 + +require ( + github.com/jarcoal/httpmock v1.3.1 + github.com/joho/godotenv v1.5.1 + golang.org/x/time v0.3.0 +) + +require golang.org/x/net v0.18.0 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..ff43cec --- /dev/null +++ b/go.sum @@ -0,0 +1,12 @@ +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/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww= +github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g= +github.com/maxatome/go-testdeep v1.12.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM= +golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= +golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/main.go b/main.go new file mode 100644 index 0000000..da29a2c --- /dev/null +++ b/main.go @@ -0,0 +1,4 @@ +package main + +func main() { +} diff --git a/pkg/e621/builder/dmail.go b/pkg/e621/builder/dmail.go new file mode 100644 index 0000000..f3bd05b --- /dev/null +++ b/pkg/e621/builder/dmail.go @@ -0,0 +1,79 @@ +package builder + +import ( + "context" + "strconv" + + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/endpoints" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/utils" +) + +type DMailsBuilder interface { + ByTitle(title string) DMailsBuilder + ByBody(body string) DMailsBuilder + ByToName(toName string) DMailsBuilder + ByFromName(fromName string) DMailsBuilder + SetLimit(limit int) DMailsBuilder + PageNumber(number int) DMailsBuilder + Execute() ([]model.DMail, error) +} + +type getDMails struct { + requestContext model.RequestContext + query map[string]string + id int +} + +func NewGetDMailsBuilder(requestContext model.RequestContext) DMailsBuilder { + dMailsBuilder := &getDMails{ + requestContext: requestContext, + query: make(map[string]string), + } + + return dMailsBuilder.SetLimit(utils.E621_MAX_POST_COUNT) +} + +func (g getDMails) ByTitle(title string) DMailsBuilder { + g.query["search[title_matches]"] = title + return g +} + +func (g getDMails) ByBody(body string) DMailsBuilder { + g.query["search[message_matches]"] = body + return g +} + +func (g getDMails) ByToName(toName string) DMailsBuilder { + g.query["search[to_name]"] = toName + return g +} + +func (g getDMails) ByFromName(fromName string) DMailsBuilder { + g.query["search[from_name]"] = fromName + return g +} + +func (g getDMails) SetLimit(limit int) DMailsBuilder { + g.query["limit"] = strconv.Itoa(limit) + return g +} + +func (g getDMails) PageNumber(number int) DMailsBuilder { + g.query["page"] = strconv.Itoa(number) + return g +} + +func (g getDMails) Execute() ([]model.DMail, error) { + if g.requestContext.RateLimiter != nil { + err := g.requestContext.RateLimiter.Wait(context.Background()) + if err != nil { + return nil, err + } + } + favorites, err := endpoints.GetAllDmails(g.requestContext, g.query) + if err != nil { + return nil, err + } + return favorites, nil +} diff --git a/pkg/e621/builder/favorite.go b/pkg/e621/builder/favorite.go new file mode 100644 index 0000000..5fb6de6 --- /dev/null +++ b/pkg/e621/builder/favorite.go @@ -0,0 +1,66 @@ +package builder + +import ( + "context" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/endpoints" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model" + "strconv" +) + +// FavoritesBuilder represents a builder for constructing queries to retrieve user's favorite posts. +type FavoritesBuilder interface { + // SetUserID sets the user ID for the query. + SetUserID(userID model.UserID) FavoritesBuilder + // SetLimit sets the maximum number of favorite posts to retrieve. + SetLimit(limitFavorites int) FavoritesBuilder + // Page sets the page number for paginated results. + Page(pageNumber int) FavoritesBuilder + // Execute sends the constructed query and returns a slice of favorite posts and an error if any. + Execute() ([]model.Post, error) +} + +// NewGetFavoritesBuilder creates a new FavoritesBuilder with the provided RequestContext. +func NewGetFavoritesBuilder(requestContext model.RequestContext) FavoritesBuilder { + return &getFavorites{ + requestContext: requestContext, + query: make(map[string]string), + } +} + +type getFavorites struct { + query map[string]string + requestContext model.RequestContext +} + +// SetUserID sets the user ID for the query. +func (g *getFavorites) SetUserID(userID model.UserID) FavoritesBuilder { + g.query["user_id"] = strconv.Itoa(int(userID)) + return g +} + +// SetLimit sets the maximum number of favorite posts to retrieve. +func (g *getFavorites) SetLimit(limitFavorites int) FavoritesBuilder { + g.query["limit"] = strconv.Itoa(limitFavorites) + return g +} + +// Page sets the page number for paginated results. +func (g *getFavorites) Page(pageNumber int) FavoritesBuilder { + g.query["page"] = strconv.Itoa(pageNumber) + return g +} + +// Execute sends the constructed query and returns a slice of favorite posts and an error if any. +func (g *getFavorites) Execute() ([]model.Post, error) { + if g.requestContext.RateLimiter != nil { + err := g.requestContext.RateLimiter.Wait(context.Background()) + if err != nil { + return nil, err + } + } + favorites, err := endpoints.GetFavorites(g.requestContext, g.query) + if err != nil { + return nil, err + } + return favorites, nil +} diff --git a/pkg/e621/builder/favorite_test.go b/pkg/e621/builder/favorite_test.go new file mode 100644 index 0000000..f028643 --- /dev/null +++ b/pkg/e621/builder/favorite_test.go @@ -0,0 +1,48 @@ +package builder + +import ( + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/utils" + "github.com/jarcoal/httpmock" + "net/http" + "testing" +) + +func TestGetFavorites(t *testing.T) { + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + response, err := utils.LoadJsonTestData[model.PostResponse]("../../../tests/posts.json") + if err != nil { + t.Error(err) + return + } + + jsonResponser, err := httpmock.NewJsonResponder(200, response) + if err != nil { + t.Error(err) + return + } + httpmock.RegisterResponder("GET", "https://e621.net/favorites.json", jsonResponser) + + requestContext := model.RequestContext{ + Client: http.Client{}, + Host: "https://e621.net", + UserAgent: "Go-e621-SDK (@username)", + Username: "memo", + APIKey: "123456", + } + getFavorites := NewGetFavoritesBuilder(requestContext) + posts, err := getFavorites.SetLimit(3).Execute() + if err != nil { + t.Error(err) + return + } + + if posts[0].ID == response.Posts[0].ID && posts[1].File.Md5 == response.Posts[1].File.Md5 && posts[2].File.EXT == response.Posts[2].File.EXT { + return + } + + t.Errorf("Respons did not match mock data:\nOriginal: %v\nMock: %v", posts, response) + +} diff --git a/pkg/e621/builder/note.go b/pkg/e621/builder/note.go new file mode 100644 index 0000000..02e5a9b --- /dev/null +++ b/pkg/e621/builder/note.go @@ -0,0 +1,47 @@ +package builder + +import ( + "context" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/endpoints" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model" + "strconv" +) + +// NoteBuilder represents a builder for constructing queries to retrieve a specific note. +type NoteBuilder interface { + // SetNoteID sets the note ID for the query. + SetNoteID(noteID int) NoteBuilder + // Execute sends the constructed query and returns the requested note and an error, if any. + Execute() (*model.Note, error) +} + +// NewGetNoteBuilder creates a new NoteBuilder with the provided RequestContext. +func NewGetNoteBuilder(requestContext model.RequestContext) NoteBuilder { + return &getNote{requestContext: requestContext} +} + +type getNote struct { + requestContext model.RequestContext + noteID int +} + +// SetNoteID sets the note ID for the query. +func (g *getNote) SetNoteID(noteID int) NoteBuilder { + g.noteID = noteID + return g +} + +// Execute sends the constructed query and returns the requested note and an error, if any. +func (g *getNote) Execute() (*model.Note, error) { + if g.requestContext.RateLimiter != nil { + err := g.requestContext.RateLimiter.Wait(context.Background()) + if err != nil { + return nil, err + } + } + note, err := endpoints.GetNote(g.requestContext, strconv.Itoa(g.noteID)) + if err != nil { + return nil, err + } + return ¬e, nil +} diff --git a/pkg/e621/builder/note_test.go b/pkg/e621/builder/note_test.go new file mode 100644 index 0000000..e1de9b9 --- /dev/null +++ b/pkg/e621/builder/note_test.go @@ -0,0 +1,49 @@ +package builder + +import ( + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/utils" + "github.com/jarcoal/httpmock" + "net/http" + "testing" +) + +func TestGetNote(t *testing.T) { + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + response, err := utils.LoadJsonTestData[model.Note]("../../../tests/note.json") + if err != nil { + t.Error(err) + return + } + + responser, err := httpmock.NewJsonResponder(200, response) + if err != nil { + t.Error(err) + return + } + httpmock.RegisterResponder("GET", "https://e621.net/notes/36957.json", responser) + + requestContext := model.RequestContext{ + Client: http.Client{}, + Host: "https://e621.net", + UserAgent: "Go-e621-SDK (@username)", + Username: "memo", + APIKey: "123456", + } + + getNote := NewGetNoteBuilder(requestContext) + pool, err := getNote.SetNoteID(36957).Execute() + if err != nil { + t.Error(err) + return + } + + if pool.ID == response.ID && pool.Body == response.Body { + return + } + + t.Errorf("Respons did not match mock data:\nOriginal: %v\nMock: %v", pool, response) + +} diff --git a/pkg/e621/builder/notes.go b/pkg/e621/builder/notes.go new file mode 100644 index 0000000..f4d5545 --- /dev/null +++ b/pkg/e621/builder/notes.go @@ -0,0 +1,98 @@ +package builder + +import ( + "context" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/endpoints" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model" + "strconv" +) + +// NotesBuilder represents a builder for constructing queries to retrieve notes. +type NotesBuilder interface { + // SearchInBody sets the query to search for notes containing a specific text in their body. + SearchInBody(body string) NotesBuilder + // NoteID sets the query to search for a note with a specific note ID. + NoteID(noteID int) NotesBuilder + // SearchTags sets the query to search for notes containing specific tags. + SearchTags(tags string) NotesBuilder + // SearchCreatorID sets the query to search for notes created by a specific user (by their user ID). + SearchCreatorID(creatorID int) NotesBuilder + // SearchCreatorName sets the query to search for notes created by a specific user (by their username). + SearchCreatorName(creatorName string) NotesBuilder + // Active sets whether to search for active or inactive notes. + Active(isActive bool) NotesBuilder + // SetLimit sets the maximum number of notes to retrieve. + SetLimit(limitNotes int) NotesBuilder + // Execute sends the constructed query and returns a slice of notes and an error, if any. + Execute() ([]model.Note, error) +} + +// NewGetNotesBuilder creates a new NotesBuilder with the provided RequestContext. +func NewGetNotesBuilder(requestContext model.RequestContext) NotesBuilder { + return &getNotes{ + requestContext: requestContext, + query: make(map[string]string), + } +} + +type getNotes struct { + requestContext model.RequestContext + query map[string]string +} + +// SearchInBody sets the query to search for notes containing a specific text in their body. +func (g *getNotes) SearchInBody(body string) NotesBuilder { + g.query["search[body_matches]"] = body + return g +} + +// NoteID sets the query to search for a note with a specific note ID. +func (g *getNotes) NoteID(noteID int) NotesBuilder { + g.query["search[post_id]"] = strconv.Itoa(noteID) + return g +} + +// SearchTags sets the query to search for notes containing specific tags. +func (g *getNotes) SearchTags(tags string) NotesBuilder { + g.query["search[post_tags_match]"] = tags + return g +} + +// SearchCreatorID sets the query to search for notes created by a specific user (by their user ID). +func (g *getNotes) SearchCreatorID(creatorID int) NotesBuilder { + g.query["search[creator_id]"] = strconv.Itoa(creatorID) + return g +} + +// SearchCreatorName sets the query to search for notes created by a specific user (by their username). +func (g *getNotes) SearchCreatorName(creatorName string) NotesBuilder { + g.query["search[creator_name]"] = creatorName + return g +} + +// Active sets whether to search for active or inactive notes. +func (g *getNotes) Active(isActive bool) NotesBuilder { + g.query["search[is_active]"] = strconv.FormatBool(isActive) + return g +} + +// SetLimit sets the maximum number of notes to retrieve. +func (g *getNotes) SetLimit(limitNotes int) NotesBuilder { + g.query["limit"] = strconv.Itoa(limitNotes) + return g +} + +// Execute sends the constructed query and returns a slice of notes and an error, if any. +func (g *getNotes) Execute() ([]model.Note, error) { + if g.requestContext.RateLimiter != nil { + err := g.requestContext.RateLimiter.Wait(context.Background()) + if err != nil { + return nil, err + } + } + notes, err := endpoints.GetNotes(g.requestContext, g.query) + if err != nil { + return nil, err + } + return notes, nil +} diff --git a/pkg/e621/builder/notes_test.go b/pkg/e621/builder/notes_test.go new file mode 100644 index 0000000..e7b61c0 --- /dev/null +++ b/pkg/e621/builder/notes_test.go @@ -0,0 +1,49 @@ +package builder + +import ( + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/utils" + "github.com/jarcoal/httpmock" + "net/http" + "testing" +) + +func TestGetNotes(t *testing.T) { + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + response, err := utils.LoadJsonTestData[[]model.Note]("../../../tests/notes.json") + if err != nil { + t.Error(err) + return + } + + responser, err := httpmock.NewJsonResponder(200, response) + if err != nil { + t.Error(err) + return + } + httpmock.RegisterResponder("GET", "https://e621.net/notes.json", responser) + + requestContext := model.RequestContext{ + Client: http.Client{}, + Host: "https://e621.net", + UserAgent: "Go-e621-SDK (@username)", + Username: "memo", + APIKey: "123456", + } + + getNotes := NewGetNotesBuilder(requestContext) + pools, err := getNotes.SetLimit(3).Execute() + if err != nil { + t.Error(err) + return + } + + if pools[0].ID == response[0].ID && pools[1].Body == response[1].Body && pools[2].UpdatedAt == response[2].UpdatedAt { + return + } + + t.Errorf("Respons did not match mock data:\nOriginal: %v\nMock: %v", pools, response) + +} diff --git a/pkg/e621/builder/pool.go b/pkg/e621/builder/pool.go new file mode 100644 index 0000000..53f9530 --- /dev/null +++ b/pkg/e621/builder/pool.go @@ -0,0 +1,47 @@ +package builder + +import ( + "context" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/endpoints" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model" + "strconv" +) + +// PoolBuilder represents a builder for constructing queries to retrieve a specific pool. +type PoolBuilder interface { + // ID sets the pool ID for the query. + ID(poolID int) PoolBuilder + // Execute sends the constructed query and returns the requested pool and an error, if any. + Execute() (model.Pool, error) +} + +// NewGetPoolBuilder creates a new PoolBuilder with the provided RequestContext. +func NewGetPoolBuilder(requestContext model.RequestContext) PoolBuilder { + return &getPool{requestContext: requestContext} +} + +type getPool struct { + requestContext model.RequestContext + id int +} + +// ID sets the pool ID for the query. +func (g *getPool) ID(poolID int) PoolBuilder { + g.id = poolID + return g +} + +// Execute sends the constructed query and returns the requested pool and an error, if any. +func (g *getPool) Execute() (model.Pool, error) { + if g.requestContext.RateLimiter != nil { + err := g.requestContext.RateLimiter.Wait(context.Background()) + if err != nil { + return model.Pool{}, err + } + } + pool, err := endpoints.GetPool(g.requestContext, strconv.Itoa(g.id)) + if err != nil { + return model.Pool{}, err + } + return pool, nil +} diff --git a/pkg/e621/builder/pool_test.go b/pkg/e621/builder/pool_test.go new file mode 100644 index 0000000..157a424 --- /dev/null +++ b/pkg/e621/builder/pool_test.go @@ -0,0 +1,49 @@ +package builder + +import ( + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/utils" + "github.com/jarcoal/httpmock" + "net/http" + "testing" +) + +func TestGetPool(t *testing.T) { + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + response, err := utils.LoadJsonTestData[model.Pool]("../../../tests/pool.json") + if err != nil { + t.Error(err) + return + } + + responser, err := httpmock.NewJsonResponder(200, response) + if err != nil { + t.Error(err) + return + } + httpmock.RegisterResponder("GET", "https://e621.net/pools/36957.json", responser) + + requestContext := model.RequestContext{ + Client: http.Client{}, + Host: "https://e621.net", + UserAgent: "Go-e621-SDK (@username)", + Username: "memo", + APIKey: "123456", + } + + getPool := NewGetPoolBuilder(requestContext) + pool, err := getPool.ID(36957).Execute() + if err != nil { + t.Error(err) + return + } + + if pool.ID == response.ID && pool.Name == response.Name { + return + } + + t.Errorf("Respons did not match mock data:\nOriginal: %v\nMock: %v", pool, response) + +} diff --git a/pkg/e621/builder/pools.go b/pkg/e621/builder/pools.go new file mode 100644 index 0000000..99ba6cc --- /dev/null +++ b/pkg/e621/builder/pools.go @@ -0,0 +1,114 @@ +package builder + +import ( + "context" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/endpoints" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model" + "strconv" +) + +// PoolsBuilder represents a builder for constructing queries to retrieve pools. +type PoolsBuilder interface { + // SearchName sets the query to search for pools with a specific name. + SearchName(name string) PoolsBuilder + // SearchID sets the query to search for pools with specific pool IDs. + SearchID(poolIDs string) PoolsBuilder + // SearchDescription sets the query to search for pools with specific descriptions. + SearchDescription(description string) PoolsBuilder + // SearchCreatorID sets the query to search for pools created by a specific user (by their user ID). + SearchCreatorID(creatorID int) PoolsBuilder + // SearchCreatorName sets the query to search for pools created by a specific user (by their username). + SearchCreatorName(creatorName string) PoolsBuilder + // Active sets whether to search for active or inactive pools. + Active(isActive bool) PoolsBuilder + // SearchCategory sets the query to search for pools in a specific category. + SearchCategory(category model.PoolCategory) PoolsBuilder + // Order sets the ordering of the retrieved pools. + Order(order model.PoolOrder) PoolsBuilder + // SetLimit sets the maximum number of pools to retrieve. + SetLimit(limitPools int) PoolsBuilder + // Execute sends the constructed query and returns a slice of pools and an error, if any. + Execute() ([]model.Pool, error) +} + +// NewGetPoolsBuilder creates a new PoolsBuilder with the provided RequestContext. +func NewGetPoolsBuilder(requestContext model.RequestContext) PoolsBuilder { + return &getPools{ + requestContext: requestContext, + query: make(map[string]string), + } +} + +type getPools struct { + requestContext model.RequestContext + query map[string]string +} + +// SearchName sets the query to search for pools with a specific name. +func (g *getPools) SearchName(name string) PoolsBuilder { + g.query["search[name_matches]"] = name + return g +} + +// SearchID sets the query to search for pools with specific pool IDs. +func (g *getPools) SearchID(poolIDs string) PoolsBuilder { + g.query["search[id]"] = poolIDs + return g +} + +// SearchDescription sets the query to search for pools with specific descriptions. +func (g *getPools) SearchDescription(description string) PoolsBuilder { + g.query["search[description_matches]"] = description + return g +} + +// SearchCreatorID sets the query to search for pools created by a specific user (by their user ID). +func (g *getPools) SearchCreatorID(creatorID int) PoolsBuilder { + g.query["search[creator_id]"] = strconv.Itoa(creatorID) + return g +} + +// SearchCreatorName sets the query to search for pools created by a specific user (by their username). +func (g *getPools) SearchCreatorName(creatorName string) PoolsBuilder { + g.query["search[creator_name]"] = creatorName + return g +} + +// Active sets whether to search for active or inactive pools. +func (g *getPools) Active(isActive bool) PoolsBuilder { + g.query["search[is_active]"] = strconv.FormatBool(isActive) + return g +} + +// SearchCategory sets the query to search for pools in a specific category. +func (g *getPools) SearchCategory(category model.PoolCategory) PoolsBuilder { + g.query["search[category]"] = string(category) + return g +} + +// Order sets the ordering of the retrieved pools. +func (g *getPools) Order(order model.PoolOrder) PoolsBuilder { + g.query["search[order]"] = string(order) + return g +} + +// SetLimit sets the maximum number of pools to retrieve. +func (g *getPools) SetLimit(limitPools int) PoolsBuilder { + g.query["limit"] = strconv.Itoa(limitPools) + return g +} + +// Execute sends the constructed query and returns a slice of pools and an error, if any. +func (g *getPools) Execute() ([]model.Pool, error) { + if g.requestContext.RateLimiter != nil { + err := g.requestContext.RateLimiter.Wait(context.Background()) + if err != nil { + return nil, err + } + } + pools, err := endpoints.GetPools(g.requestContext, g.query) + if err != nil { + return nil, err + } + return pools, nil +} diff --git a/pkg/e621/builder/pools_test.go b/pkg/e621/builder/pools_test.go new file mode 100644 index 0000000..11ed09c --- /dev/null +++ b/pkg/e621/builder/pools_test.go @@ -0,0 +1,48 @@ +package builder + +import ( + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/utils" + "github.com/jarcoal/httpmock" + "net/http" + "testing" +) + +func TestGetPools(t *testing.T) { + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + response, err := utils.LoadJsonTestData[[]model.Pool]("../../../tests/pools.json") + if err != nil { + t.Error(err) + return + } + responser, err := httpmock.NewJsonResponder(200, response) + if err != nil { + t.Error(err) + return + } + httpmock.RegisterResponder("GET", "https://e621.net/pools.json", responser) + + requestContext := model.RequestContext{ + Client: http.Client{}, + Host: "https://e621.net", + UserAgent: "Go-e621-SDK (@username)", + Username: "memo", + APIKey: "123456", + } + + getPools := NewGetPoolsBuilder(requestContext) + pools, err := getPools.SetLimit(4).Execute() + if err != nil { + t.Error(err) + return + } + + if pools[0].ID == response[0].ID && pools[1].Name == response[1].Name && pools[2].UpdatedAt == response[2].UpdatedAt { + return + } + + t.Errorf("Respons did not match mock data:\nOriginal: %v\nMock: %v", pools, response) + +} diff --git a/pkg/e621/builder/post.go b/pkg/e621/builder/post.go new file mode 100644 index 0000000..7b04454 --- /dev/null +++ b/pkg/e621/builder/post.go @@ -0,0 +1,47 @@ +package builder + +import ( + "context" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/endpoints" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model" + "strconv" +) + +// PostBuilder represents a builder for constructing queries to retrieve a specific post. +type PostBuilder interface { + // SetPostID sets the post ID for the query. + SetPostID(postID model.PostID) PostBuilder + // Execute sends the constructed query and returns the requested post and an error, if any. + Execute() (*model.Post, error) +} + +// NewGetPostBuilder creates a new PostBuilder with the provided RequestContext. +func NewGetPostBuilder(requestContext model.RequestContext) PostBuilder { + return &getPost{requestContext: requestContext} +} + +type getPost struct { + requestContext model.RequestContext + postID model.PostID +} + +// SetPostID sets the post ID for the query. +func (g *getPost) SetPostID(postID model.PostID) PostBuilder { + g.postID = postID + return g +} + +// Execute sends the constructed query and returns the requested post and an error, if any. +func (g *getPost) Execute() (*model.Post, error) { + if g.requestContext.RateLimiter != nil { + err := g.requestContext.RateLimiter.Wait(context.Background()) + if err != nil { + return nil, err + } + } + post, err := endpoints.GetPost(g.requestContext, strconv.Itoa(int(g.postID))) + if err != nil { + return nil, err + } + return &post, nil +} diff --git a/pkg/e621/builder/post_test.go b/pkg/e621/builder/post_test.go new file mode 100644 index 0000000..c8f3340 --- /dev/null +++ b/pkg/e621/builder/post_test.go @@ -0,0 +1,54 @@ +package builder + +import ( + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/utils" + "github.com/jarcoal/httpmock" + "net/http" + "testing" +) + +func TestGetPost(t *testing.T) { + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + data, err := utils.LoadJsonTestData[model.Post]("../../../tests/post.json") + if err != nil { + t.Error(err) + return + } + + response := model.PostResponse{ + Post: data, + Posts: nil, + } + + responser, err := httpmock.NewJsonResponder(200, response) + if err != nil { + t.Error(err) + return + } + httpmock.RegisterResponder("GET", "https://e621.net/posts/658415636580.json", responser) + + requestContext := model.RequestContext{ + Client: http.Client{}, + Host: "https://e621.net", + UserAgent: "Go-e621-SDK (@username)", + Username: "memo", + APIKey: "123456", + } + + getPost := NewGetPostBuilder(requestContext) + post, err := getPost.SetPostID(658415636580).Execute() + if err != nil { + t.Error(err) + return + } + + if post.ID == response.Post.ID && post.File.URL == response.Post.File.URL { + return + } + + t.Errorf("Respons did not match mock data:\nOriginal: %v\nMock: %v", post, response) + +} diff --git a/pkg/e621/builder/posts.go b/pkg/e621/builder/posts.go new file mode 100644 index 0000000..45fa0cb --- /dev/null +++ b/pkg/e621/builder/posts.go @@ -0,0 +1,85 @@ +package builder + +import ( + "context" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/endpoints" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/utils" + "strconv" +) + +// PostsBuilder represents a builder for constructing queries to retrieve posts. +type PostsBuilder interface { + // Tags sets the query to search for posts with specific tags. + Tags(tags string) PostsBuilder + // PageAfter sets the query to retrieve posts after a specific post ID. + PageAfter(postID model.PostID) PostsBuilder + // PageBefore sets the query to retrieve posts before a specific post ID. + PageBefore(postID model.PostID) PostsBuilder + // PageNumber sets the query to retrieve posts on a specific page number. + PageNumber(number int) PostsBuilder + // SetLimit sets the maximum number of posts to retrieve. + SetLimit(limitUser int) PostsBuilder + // Execute sends the constructed query and returns a slice of posts and an error, if any. + Execute() ([]model.Post, error) +} + +// NewGetPostsBuilder creates a new PostsBuilder with the provided RequestContext. +func NewGetPostsBuilder(requestContext model.RequestContext) PostsBuilder { + postBuilder := &getPosts{ + requestContext: requestContext, + query: make(map[string]string), + } + + return postBuilder.SetLimit(utils.E621_MAX_POST_COUNT) +} + +type getPosts struct { + requestContext model.RequestContext + query map[string]string +} + +// Tags sets the query to search for posts with specific tags. +func (g *getPosts) Tags(tags string) PostsBuilder { + g.query["tags"] = tags + return g +} + +// PageAfter sets the query to retrieve posts after a specific post ID. +func (g *getPosts) PageAfter(postID model.PostID) PostsBuilder { + g.query["page"] = "a" + strconv.Itoa(int(postID)) + return g +} + +// PageBefore sets the query to retrieve posts before a specific post ID. +func (g *getPosts) PageBefore(postID model.PostID) PostsBuilder { + g.query["page"] = "b" + strconv.Itoa(int(postID)) + return g +} + +// PageNumber sets the query to retrieve posts on a specific page number. +func (g *getPosts) PageNumber(number int) PostsBuilder { + g.query["page"] = strconv.Itoa(number) + return g +} + +// SetLimit sets the maximum number of posts to retrieve. +func (g *getPosts) SetLimit(limitUser int) PostsBuilder { + g.query["limit"] = strconv.Itoa(limitUser) + return g +} + +// Execute sends the constructed query and returns a slice of posts and an error, if any. +func (g *getPosts) Execute() ([]model.Post, error) { + if g.requestContext.RateLimiter != nil { + err := g.requestContext.RateLimiter.Wait(context.Background()) + if err != nil { + return nil, err + } + } + posts, err := endpoints.GetPosts(g.requestContext, g.query) + if err != nil { + return nil, err + } + return posts, err +} diff --git a/pkg/e621/builder/posts_test.go b/pkg/e621/builder/posts_test.go new file mode 100644 index 0000000..8a912c0 --- /dev/null +++ b/pkg/e621/builder/posts_test.go @@ -0,0 +1,49 @@ +package builder + +import ( + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/utils" + "github.com/jarcoal/httpmock" + "net/http" + "testing" +) + +func TestGetPosts(t *testing.T) { + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + response, err := utils.LoadJsonTestData[model.PostResponse]("../../../tests/posts.json") + if err != nil { + t.Error(err) + return + } + + responser, err := httpmock.NewJsonResponder(200, response) + if err != nil { + t.Error(err) + return + } + httpmock.RegisterResponder("GET", "https://e621.net/posts.json", responser) + + requestContext := model.RequestContext{ + Client: http.Client{}, + Host: "https://e621.net", + UserAgent: "Go-e621-SDK (@username)", + Username: "memo", + APIKey: "123456", + } + + getPosts := NewGetPostsBuilder(requestContext) + posts, err := getPosts.SetLimit(3).Execute() + if err != nil { + t.Error(err) + return + } + + if posts[0].ID == response.Posts[0].ID && posts[1].File.Md5 == response.Posts[1].File.Md5 && posts[2].File.EXT == response.Posts[2].File.EXT { + return + } + + t.Errorf("Respons did not match mock data:\nOriginal: %v\nMock: %v", posts, response) + +} diff --git a/pkg/e621/builder/tag.go b/pkg/e621/builder/tag.go new file mode 100644 index 0000000..d7200f7 --- /dev/null +++ b/pkg/e621/builder/tag.go @@ -0,0 +1,47 @@ +package builder + +import ( + "context" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/endpoints" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model" + "strconv" +) + +// TagBuilder represents a builder for constructing queries to retrieve a specific tag. +type TagBuilder interface { + // SetTagID sets the tag ID for the query. + SetTagID(tagID int) TagBuilder + // Execute sends the constructed query and returns the requested tag and an error, if any. + Execute() (model.Tag, error) +} + +// NewGetTagBuilder creates a new TagBuilder with the provided RequestContext. +func NewGetTagBuilder(requestContext model.RequestContext) TagBuilder { + return &getTag{requestContext: requestContext} +} + +type getTag struct { + requestContext model.RequestContext + tagID int +} + +// SetTagID sets the tag ID for the query. +func (g *getTag) SetTagID(tagID int) TagBuilder { + g.tagID = tagID + return g +} + +// Execute sends the constructed query and returns the requested tag and an error, if any. +func (g *getTag) Execute() (model.Tag, error) { + if g.requestContext.RateLimiter != nil { + err := g.requestContext.RateLimiter.Wait(context.Background()) + if err != nil { + return model.Tag{}, err + } + } + tag, err := endpoints.GetTag(g.requestContext, strconv.Itoa(g.tagID)) + if err != nil { + return model.Tag{}, err + } + return tag, nil +} diff --git a/pkg/e621/builder/tag_test.go b/pkg/e621/builder/tag_test.go new file mode 100644 index 0000000..49a3678 --- /dev/null +++ b/pkg/e621/builder/tag_test.go @@ -0,0 +1,49 @@ +package builder + +import ( + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/utils" + "github.com/jarcoal/httpmock" + "net/http" + "testing" +) + +func TestGetTag(t *testing.T) { + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + response, err := utils.LoadJsonTestData[model.Tag]("../../../tests/tag.json") + if err != nil { + t.Error(err) + return + } + + responser, err := httpmock.NewJsonResponder(200, response) + if err != nil { + t.Error(err) + return + } + httpmock.RegisterResponder("GET", "https://e621.net/tags/165165.json", responser) + + requestContext := model.RequestContext{ + Client: http.Client{}, + Host: "https://e621.net", + UserAgent: "Go-e621-SDK (@username)", + Username: "memo", + APIKey: "123456", + } + + getTag := NewGetTagBuilder(requestContext) + tag, err := getTag.SetTagID(165165).Execute() + if err != nil { + t.Error(err) + return + } + + if tag.ID == response.ID && tag.Name == response.Name && tag.CreatedAt == response.CreatedAt { + return + } + + t.Errorf("Respons did not match mock data:\nOriginal: %v\nMock: %v", tag, response) + +} diff --git a/pkg/e621/builder/tags.go b/pkg/e621/builder/tags.go new file mode 100644 index 0000000..54b596f --- /dev/null +++ b/pkg/e621/builder/tags.go @@ -0,0 +1,103 @@ +package builder + +import ( + "context" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/endpoints" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model" + "strconv" +) + +// TagsBuilder represents a builder for constructing queries to retrieve tags. +type TagsBuilder interface { + // SearchName sets the query to search for tags with specific names. + SearchName(name string) TagsBuilder + // SearchCategory sets the query to search for tags in a specific category. + SearchCategory(category model.TagCategory) TagsBuilder + // Order sets the query to order tags by a specific criterion. + Order(order string) TagsBuilder + // HideEmpty sets the query to filter out tags that are empty. + HideEmpty(hideEmpty bool) TagsBuilder + // Wiki sets the query to filter tags that have a wiki. + Wiki(hasWiki bool) TagsBuilder + // Artist sets the query to filter tags that have an artist page. + Artist(hasArtistPage bool) TagsBuilder + // SetPage sets the query to retrieve tags from a specific page number. + SetPage(pageNumber int) TagsBuilder + // SetLimit sets the maximum number of tags to retrieve. + SetLimit(limitUser int) TagsBuilder + // Execute sends the constructed query and returns a slice of tags and an error, if any. + Execute() ([]model.Tag, error) +} + +// NewGetTagsBuilder creates a new TagsBuilder with the provided RequestContext. +func NewGetTagsBuilder(requestContext model.RequestContext) TagsBuilder { + return &getTags{requestContext: requestContext, query: make(map[string]string)} +} + +type getTags struct { + requestContext model.RequestContext + query map[string]string +} + +// SearchName sets the query to search for tags with specific names. +func (g *getTags) SearchName(name string) TagsBuilder { + g.query["search[name_matches]"] = name + return g +} + +// SearchCategory sets the query to search for tags in a specific category. +func (g *getTags) SearchCategory(category model.TagCategory) TagsBuilder { + g.query["search[category]"] = strconv.Itoa(int(category)) + return g +} + +// Order sets the query to order tags by a specific criterion. +func (g *getTags) Order(order string) TagsBuilder { + g.query["search[order]"] = order + return g +} + +// HideEmpty sets the query to filter out tags that are empty. +func (g *getTags) HideEmpty(hideEmpty bool) TagsBuilder { + g.query["search[hide_empty]"] = strconv.FormatBool(hideEmpty) + return g +} + +// Wiki sets the query to filter tags that have a wiki. +func (g *getTags) Wiki(hasWiki bool) TagsBuilder { + g.query["search[has_wiki]"] = strconv.FormatBool(hasWiki) + return g +} + +// Artist sets the query to filter tags that have an artist page. +func (g *getTags) Artist(hasArtistPage bool) TagsBuilder { + g.query["search[has_artist]"] = strconv.FormatBool(hasArtistPage) + return g +} + +// SetPage sets the query to retrieve tags from a specific page number. +func (g *getTags) SetPage(pageNumber int) TagsBuilder { + g.query["page"] = strconv.Itoa(pageNumber) + return g +} + +// SetLimit sets the maximum number of tags to retrieve. +func (g *getTags) SetLimit(limitUser int) TagsBuilder { + g.query["limit"] = strconv.Itoa(limitUser) + return g +} + +// Execute sends the constructed query and returns a slice of tags and an error, if any. +func (g *getTags) Execute() ([]model.Tag, error) { + if g.requestContext.RateLimiter != nil { + err := g.requestContext.RateLimiter.Wait(context.Background()) + if err != nil { + return nil, err + } + } + tags, err := endpoints.GetTags(g.requestContext, g.query) + if err != nil { + return nil, err + } + return tags, err +} diff --git a/pkg/e621/builder/user.go b/pkg/e621/builder/user.go new file mode 100644 index 0000000..102c9bd --- /dev/null +++ b/pkg/e621/builder/user.go @@ -0,0 +1,46 @@ +package builder + +import ( + "context" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/endpoints" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model" +) + +// UserBuilder represents a builder for constructing queries to retrieve user information. +type UserBuilder interface { + // SetUsername sets the username for the query to retrieve user information. + SetUsername(username string) UserBuilder + // Execute sends the constructed query and returns the requested user information and an error, if any. + Execute() (model.User, error) +} + +// NewGetUserBuilder creates a new UserBuilder with the provided RequestContext. +func NewGetUserBuilder(requestContext model.RequestContext) UserBuilder { + return &getUser{requestContext: requestContext} +} + +type getUser struct { + requestContext model.RequestContext + username string +} + +// SetUsername sets the username for the query to retrieve user information. +func (g *getUser) SetUsername(username string) UserBuilder { + g.username = username + return g +} + +// Execute sends the constructed query and returns the requested user information and an error, if any. +func (g *getUser) Execute() (model.User, error) { + if g.requestContext.RateLimiter != nil { + err := g.requestContext.RateLimiter.Wait(context.Background()) + if err != nil { + return model.User{}, err + } + } + user, err := endpoints.GetUser(g.requestContext, g.username) + if err != nil { + return model.User{}, err + } + return user, nil +} diff --git a/pkg/e621/builder/user_test.go b/pkg/e621/builder/user_test.go new file mode 100644 index 0000000..1c0664d --- /dev/null +++ b/pkg/e621/builder/user_test.go @@ -0,0 +1,49 @@ +package builder + +import ( + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/utils" + "github.com/jarcoal/httpmock" + "net/http" + "testing" +) + +func TestGetUser(t *testing.T) { + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + response, err := utils.LoadJsonTestData[model.User]("../../../tests/user.json") + if err != nil { + t.Error(err) + return + } + + responser, err := httpmock.NewJsonResponder(200, response) + if err != nil { + t.Error(err) + return + } + httpmock.RegisterResponder("GET", "https://e621.net/users/MaxMustermannDer69ste.json", responser) + + requestContext := model.RequestContext{ + Client: http.Client{}, + Host: "https://e621.net", + UserAgent: "Go-e621-SDK (@username)", + Username: "memo", + APIKey: "123456", + } + + GetUser := NewGetUserBuilder(requestContext) + user, err := GetUser.SetUsername("MaxMustermannDer69ste").Execute() + if err != nil { + t.Error(err) + return + } + + if user.ID == response.ID && user.Name == response.Name && user.CreatedAt == response.CreatedAt { + return + } + + t.Errorf("Respons did not match mock data:\nOriginal: %v\nMock: %v", user, response) + +} diff --git a/pkg/e621/builder/users.go b/pkg/e621/builder/users.go new file mode 100644 index 0000000..a55443d --- /dev/null +++ b/pkg/e621/builder/users.go @@ -0,0 +1,127 @@ +package builder + +import ( + "context" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/endpoints" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model" + "strconv" +) + +// UsersBuilder represents a builder for constructing queries to retrieve user information. +type UsersBuilder interface { + // SetPage sets the page number for paginated results. + SetPage(pageNumber int) UsersBuilder + // SetLimit sets the maximum number of users to retrieve. + SetLimit(limitUser int) UsersBuilder + // SearchByName specifies a username to search for. + SearchByName(userName string) UsersBuilder + // SearchByAbout specifies a text to search in the 'about' field of user profiles. + SearchByAbout(about string) UsersBuilder + // SearchByAvatarID specifies an avatar ID to search for. + SearchByAvatarID(avatarID model.PostID) UsersBuilder + // SearchByLevel specifies a user level to filter by. + SearchByLevel(level model.UserLevel) UsersBuilder + // SearchByMinLevel specifies the minimum user level to filter by. + SearchByMinLevel(minLevel model.UserLevel) UsersBuilder + // SearchByMaxLevel specifies the maximum user level to filter by. + SearchByMaxLevel(maxLevel model.UserLevel) UsersBuilder + // SearchByCanUpload specifies whether users can upload free content. + SearchByCanUpload(canUpload bool) UsersBuilder + // SearchByIsApprover specifies whether users can approve posts. + SearchByIsApprover(isApprover bool) UsersBuilder + // SearchByOrder specifies the order in which users are retrieved. + SearchByOrder(order model.Order) UsersBuilder + // Execute sends the constructed query and returns the requested user information and an error, if any. + Execute() ([]model.User, error) +} + +// NewGetUsersBuilder creates a new UsersBuilder with the provided RequestContext. +func NewGetUsersBuilder(requestContext model.RequestContext) UsersBuilder { + return &getUsers{requestContext: requestContext, query: make(map[string]string)} +} + +type getUsers struct { + requestContext model.RequestContext + query map[string]string +} + +// SearchByName specifies a username to search for. +func (g *getUsers) SearchByName(userName string) UsersBuilder { + g.query["search[name_matches]"] = userName + return g +} + +// SearchByAbout specifies a text to search in the 'about' field of user profiles. +func (g *getUsers) SearchByAbout(about string) UsersBuilder { + g.query["search[about_me]"] = about + return g +} + +// SearchByAvatarID specifies an avatar ID to search for. +func (g *getUsers) SearchByAvatarID(avatarID model.PostID) UsersBuilder { + g.query["search[avatar_id]"] = strconv.FormatInt(int64(avatarID), 10) + return g +} + +// SearchByLevel specifies a user level to filter by. +func (g *getUsers) SearchByLevel(level model.UserLevel) UsersBuilder { + g.query["search[level]"] = strconv.Itoa(int(level)) + return g +} + +// SearchByMinLevel specifies the minimum user level to filter by. +func (g *getUsers) SearchByMinLevel(minLevel model.UserLevel) UsersBuilder { + g.query["search[min_level]"] = strconv.Itoa(int(minLevel)) + return g +} + +// SearchByMaxLevel specifies the maximum user level to filter by. +func (g *getUsers) SearchByMaxLevel(maxLevel model.UserLevel) UsersBuilder { + g.query["search[max_level]"] = strconv.Itoa(int(maxLevel)) + return g +} + +// SearchByCanUpload specifies whether users can upload free content. +func (g *getUsers) SearchByCanUpload(canUpload bool) UsersBuilder { + g.query["search[can_upload_free]"] = strconv.FormatBool(canUpload) + return g +} + +// SearchByIsApprover specifies whether users can approve posts. +func (g *getUsers) SearchByIsApprover(isApprover bool) UsersBuilder { + g.query["search[can_approve_posts]"] = strconv.FormatBool(isApprover) + return g +} + +// SearchByOrder specifies the order in which users are retrieved. +func (g *getUsers) SearchByOrder(order model.Order) UsersBuilder { + g.query["search[order]"] = string(order) + return g +} + +// SetPage sets the page number for paginated results. +func (g *getUsers) SetPage(pageNumber int) UsersBuilder { + g.query["page"] = strconv.Itoa(pageNumber) + return g +} + +// SetLimit sets the maximum number of users to retrieve. +func (g *getUsers) SetLimit(limitUser int) UsersBuilder { + g.query["limit"] = strconv.Itoa(limitUser) + return g +} + +// Execute sends the constructed query and returns the requested user information and an error, if any. +func (g *getUsers) Execute() ([]model.User, error) { + if g.requestContext.RateLimiter != nil { + err := g.requestContext.RateLimiter.Wait(context.Background()) + if err != nil { + return nil, err + } + } + users, err := endpoints.GetUsers(g.requestContext, g.query) + if err != nil { + return nil, err + } + return users, nil +} diff --git a/pkg/e621/builder/users_test.go b/pkg/e621/builder/users_test.go new file mode 100644 index 0000000..4e84221 --- /dev/null +++ b/pkg/e621/builder/users_test.go @@ -0,0 +1,49 @@ +package builder + +import ( + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/utils" + "github.com/jarcoal/httpmock" + "net/http" + "testing" +) + +func TestGetUsers(t *testing.T) { + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + response, err := utils.LoadJsonTestData[[]model.User]("../../../tests/users.json") + if err != nil { + t.Error(err) + return + } + + responser, err := httpmock.NewJsonResponder(200, response) + if err != nil { + t.Error(err) + return + } + httpmock.RegisterResponder("GET", "https://e621.net/users.json", responser) + + requestContext := model.RequestContext{ + Client: http.Client{}, + Host: "https://e621.net", + UserAgent: "Go-e621-SDK (@username)", + Username: "memo", + APIKey: "123456", + } + + getUsers := NewGetUsersBuilder(requestContext) + user, err := getUsers.Execute() + if err != nil { + t.Error(err) + return + } + + if user[0].ID == response[0].ID && user[1].Name == response[1].Name { + return + } + + t.Errorf("Respons did not match mock data:\nOriginal: %v\nMock: %v", user, response) + +} diff --git a/pkg/e621/client.go b/pkg/e621/client.go new file mode 100644 index 0000000..88911b6 --- /dev/null +++ b/pkg/e621/client.go @@ -0,0 +1,244 @@ +package e621 + +import ( + "fmt" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/builder" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/utils" + _ "github.com/joho/godotenv/autoload" + "golang.org/x/time/rate" + "math" + "net/http" + "strconv" +) + +// Client is the main client for interacting with the e621 API. +type Client struct { + RequestContext model.RequestContext +} + +// NewClient creates a new e621 client with the provided username and API key. +func NewClient(username string, apiKey string) Client { + // Create a new e621 client with the given username and API key. + return Client{ + RequestContext: model.RequestContext{ + Client: http.Client{}, + RateLimiter: rate.NewLimiter(1, 2), + Host: "https://e621.net", + UserAgent: fmt.Sprintf("Go-e621-SDK used by %s | (made by the Anthrove Team)", username), + Username: username, + APIKey: apiKey, + }, + } +} + +// GetUserBuilder returns a UserBuilder instance for creating and executing requests to retrieve user information from the e621 API. +// +// Returns: +// - builder.UserBuilder: An instance of the UserBuilder. +func (c *Client) GetUserBuilder() builder.UserBuilder { + return builder.NewGetUserBuilder(c.RequestContext) +} + +// GetUsersBuilder returns a UsersBuilder instance for creating and executing requests to retrieve multiple users' information from the e621 API. +// +// Returns: +// - builder.UsersBuilder: An instance of the UsersBuilder. +func (c *Client) GetUsersBuilder() builder.UsersBuilder { + return builder.NewGetUsersBuilder(c.RequestContext) +} + +// GetFavoritesBuilder returns a FavoritesBuilder instance for creating and executing requests to retrieve a user's favorite posts from the e621 API. +// +// Returns: +// - builder.FavoritesBuilder: An instance of the FavoritesBuilder. +func (c *Client) GetFavoritesBuilder() builder.FavoritesBuilder { + return builder.NewGetFavoritesBuilder(c.RequestContext) +} + +// GetPostBuilder returns a PostBuilder instance for creating and executing requests to retrieve post information from the e621 API. +// +// Returns: +// - builder.PostBuilder: An instance of the PostBuilder. +func (c *Client) GetPostBuilder() builder.PostBuilder { + return builder.NewGetPostBuilder(c.RequestContext) +} + +// GetPostsBuilder returns a PostsBuilder instance for creating and executing requests to retrieve multiple posts' information from the e621 API. +// +// Returns: +// - builder.PostsBuilder: An instance of the PostsBuilder. +func (c *Client) GetPostsBuilder() builder.PostsBuilder { + return builder.NewGetPostsBuilder(c.RequestContext) +} + +// GetNoteBuilder returns a NoteBuilder instance for creating and executing requests to retrieve note information from the e621 API. +// +// Returns: +// - builder.NoteBuilder: An instance of the NoteBuilder. +func (c *Client) GetNoteBuilder() builder.NoteBuilder { + return builder.NewGetNoteBuilder(c.RequestContext) +} + +// GetNotesBuilder returns a NotesBuilder instance for creating and executing requests to retrieve multiple notes' information from the e621 API. +// +// Returns: +// - builder.NotesBuilder: An instance of the NotesBuilder. +func (c *Client) GetNotesBuilder() builder.NotesBuilder { + return builder.NewGetNotesBuilder(c.RequestContext) +} + +// GetPoolBuilder returns a PoolBuilder instance for creating and executing requests to retrieve pool information from the e621 API. +// +// Returns: +// - builder.PoolBuilder: An instance of the PoolBuilder. +func (c *Client) GetPoolBuilder() builder.PoolBuilder { + return builder.NewGetPoolBuilder(c.RequestContext) +} + +// GetPoolsBuilder returns a PoolsBuilder instance for creating and executing requests to retrieve multiple pools' information from the e621 API. +// +// Returns: +// - builder.PoolsBuilder: An instance of the PoolsBuilder. +func (c *Client) GetPoolsBuilder() builder.PoolsBuilder { + return builder.NewGetPoolsBuilder(c.RequestContext) +} + +// GetTagBuilder returns a TagBuilder instance for creating and executing requests to retrieve tag information from the e621 API. +// +// Returns: +// - builder.TagBuilder: An instance of the TagBuilder. +func (c *Client) GetTagBuilder() builder.TagBuilder { + return builder.NewGetTagBuilder(c.RequestContext) +} + +// GetTagsBuilder returns a TagsBuilder instance for creating and executing requests to retrieve multiple tags' information from the e621 API. +// +// Returns: +// - builder.TagsBuilder: An instance of the TagsBuilder. +func (c *Client) GetTagsBuilder() builder.TagsBuilder { + return builder.NewGetTagsBuilder(c.RequestContext) +} + +// SetHost sets the API host for the client. +func (c *Client) SetHost(host string) *Client { + // Set the API host for the client. + c.RequestContext.Host = host + return c +} + +// SetAgentName sets the user agent name for the client. +func (c *Client) SetAgentName(userAgent string) *Client { + // Set the user agent name for the client. + c.RequestContext.UserAgent = userAgent + return c +} + +// GetUserByName returns a user builder for a given username. +func (c *Client) GetUserByName(username string) builder.UserBuilder { + // Returns a user builder for the specified username. + return builder.NewGetUserBuilder(c.RequestContext).SetUsername(username) +} + +// GetUserByID returns a user builder for a given user ID. +func (c *Client) GetUserByID(userID model.UserID) builder.UserBuilder { + // Returns a user builder for the specified user ID. + return builder.NewGetUserBuilder(c.RequestContext).SetUsername(strconv.FormatInt(int64(userID), 10)) +} + +// GetFavoritesForUser returns a favorites builder for a given username. +func (c *Client) GetFavoritesForUser(username string) (builder.FavoritesBuilder, error) { + // Returns a favorites builder for the specified username. + user, err := builder.NewGetUserBuilder(c.RequestContext).SetUsername(username).Execute() + if err != nil { + return nil, err + } + favoritesBuilder := builder.NewGetFavoritesBuilder(c.RequestContext).SetUserID(user.ID) + return favoritesBuilder, nil +} + +// GetNFavoritesForUser retrieves a specified number of favorites for a user. +func (c *Client) GetNFavoritesForUser(n int, favoriteBuilder builder.FavoritesBuilder) ([]model.Post, error) { + // Retrieves a specified number of favorite posts for a user. + if n < utils.E621_MAX_POST_COUNT { + favoriteBuilder.SetLimit(n) + } + + var favorites []model.Post + var page = 1 + for len(favorites) < n { + favoriteBuilder.Page(page) + favoriteBuilder.SetLimit(n - len(favorites)) + newFavorites, err := favoriteBuilder.Execute() + if err != nil { + return nil, err + } + if len(newFavorites) == 0 { + break + } + favorites = append(favorites, newFavorites...) + page = page + 1 + } + + return favorites, nil +} + +// GetAllFavoritesForUser retrieves all favorites for a user. +func (c *Client) GetAllFavoritesForUser(favoriteBuilder builder.FavoritesBuilder) ([]model.Post, error) { + // Retrieves all favorite posts for a user. + return c.GetNFavoritesForUser(math.MaxInt, favoriteBuilder) +} + +// GetFavoritesForUserWithTags returns a posts builder for a user's favorites with specific tags. +func (c *Client) GetFavoritesForUserWithTags(username string, tags string) builder.PostsBuilder { + // Returns a posts builder for a user's favorites with specific tags. + favoritesBuilder := builder.NewGetPostsBuilder(c.RequestContext).Tags(fmt.Sprintf("fav:%s %s", username, tags)) + return favoritesBuilder +} + +// GetPostByID returns a post builder for a specific post ID. +func (c *Client) GetPostByID(id model.PostID) builder.PostBuilder { + // Returns a post builder for a specific post ID. + return builder.NewGetPostBuilder(c.RequestContext).SetPostID(id) +} + +// GetPosts returns a posts builder for general post queries. +func (c *Client) GetPosts() builder.PostsBuilder { + // Returns a posts builder for general post queries. + return builder.NewGetPostsBuilder(c.RequestContext) +} + +// GetNPosts retrieves a specified number of posts. +func (c *Client) GetNPosts(n int, postBuilder builder.PostsBuilder) ([]model.Post, error) { + // Retrieves a specified number of posts using the provided post builder. + if n < utils.E621_MAX_POST_COUNT { + postBuilder.SetLimit(n) + } + + posts, err := postBuilder.Execute() + if err != nil { + return nil, err + } + + for len(posts) < n { + lastPostID := posts[len(posts)-1].ID + postBuilder.PageBefore(lastPostID) + postBuilder.SetLimit(n - len(posts)) + newPosts, err := postBuilder.Execute() + if err != nil { + return nil, err + } + if len(newPosts) == 0 { + break + } + posts = append(posts, newPosts...) + } + + return posts, nil +} + +// GetAllPosts retrieves all available posts using the provided post builder. +func (c *Client) GetAllPosts(postBuilder builder.PostsBuilder) ([]model.Post, error) { + // Retrieves all available posts using the provided post builder. + return c.GetNPosts(math.MaxInt, postBuilder) +} diff --git a/pkg/e621/endpoints/dbexport.go b/pkg/e621/endpoints/dbexport.go new file mode 100644 index 0000000..90f94d9 --- /dev/null +++ b/pkg/e621/endpoints/dbexport.go @@ -0,0 +1,117 @@ +package endpoints + +import ( + "fmt" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/utils" + "golang.org/x/net/html" + "net/http" + "strings" +) + +// GetDBExportList retrieves a list of files available in the e621 database export. +// +// This function performs an HTTP GET request to the e621 database export endpoint and parses +// the HTML content to extract the links to export files with the ".csv.gz" extension. +// +// Parameters: +// - requestContext: The context for the API request, including the host, user agent, username, and API key. +// +// Returns: +// - []string: A slice of file names with the ".csv.gz" extension. +// - error: An error, if any, encountered during the API request, response handling, or HTML parsing. +func GetDBExportList(requestContext model.RequestContext) ([]string, error) { + // Create a new HTTP GET request. + r, err := http.NewRequest("GET", fmt.Sprintf("https://e621.net/db_export/"), nil) + if err != nil { + return nil, err + } + + r.Header.Set("User-Agent", requestContext.UserAgent) + r.Header.Add("Accept", "application/json") + r.SetBasicAuth(requestContext.Username, requestContext.APIKey) + + // Send the request using the provided http.Client. + resp, err := requestContext.Client.Do(r) + if err != nil { + return nil, err + } + + // Check if the HTTP response status code indicates success (2xx range). + if resp.StatusCode < 200 || resp.StatusCode > 300 { + // If the status code is outside the 2xx range, return an error based on the status code. + return nil, utils.StatusCodesToError(resp.StatusCode) + } + + // Parse the HTML content + tokenizer := html.NewTokenizer(resp.Body) + var files []string + + // Iterate through the HTML tokens + for { + tokenType := tokenizer.Next() + + switch tokenType { + case html.ErrorToken: + // End of the HTML document + return files, nil + case html.StartTagToken, html.SelfClosingTagToken: + token := tokenizer.Token() + if token.Data == "a" { + // Check if the anchor tag has an href attribute + for _, attr := range token.Attr { + if attr.Key == "href" { + // Filter out the parent directory link and only add links with ".csv.gz" extension + if !strings.HasPrefix(attr.Val, "../") && strings.HasSuffix(attr.Val, ".csv.gz") { + files = append(files, attr.Val) + } + break + } + } + } + } + } +} + +// GetDBExportFile retrieves a specific file from the e621 database export. +// +// This function performs an HTTP GET request to the e621 database export endpoint to fetch a +// particular file identified by its name. +// +// Parameters: +// - requestContext: The context for the API request, including the host, user agent, username, and API key. +// - file: The name of the file to be fetched from the database export. +// +// Returns: +// - *http.Response: The HTTP response containing the requested file (probably a csv.gz). +// - error: An error, if any, encountered during the API request or response handling. +func GetDBExportFile(requestContext model.RequestContext, file string) (*http.Response, error) { + if file == "" { + return nil, fmt.Errorf("no file specified") + } + + // Create a new HTTP GET request. + r, err := http.NewRequest("GET", fmt.Sprintf("https://e621.net/db_export/%s", file), nil) + if err != nil { + return nil, err + } + + r.Header.Set("User-Agent", requestContext.UserAgent) + r.Header.Add("Accept", "application/json") + r.SetBasicAuth(requestContext.Username, requestContext.APIKey) + + // Send the request using the provided http.Client. + resp, err := requestContext.Client.Do(r) + if err != nil { + return nil, err + } + + // Check if the HTTP response status code indicates success (2xx range). + if resp.StatusCode < 200 || resp.StatusCode > 300 { + // If the status code is outside the 2xx range, return an error based on the status code. + return nil, utils.StatusCodesToError(resp.StatusCode) + } + + return resp, nil + +} diff --git a/pkg/e621/endpoints/dmail.go b/pkg/e621/endpoints/dmail.go new file mode 100644 index 0000000..c07e524 --- /dev/null +++ b/pkg/e621/endpoints/dmail.go @@ -0,0 +1,234 @@ +package endpoints + +import ( + "encoding/json" + "fmt" + "net/http" + + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/utils" +) + +// GetDmail retrieves a specific DMail by its ID. +// +// This function performs an HTTP GET request to the e621 DMail endpoint and parses +// the JSON content to extract the DMail data. +// +// Parameters: +// - requestContext: The model.RequestContext for the API request. +// - ID: The ID of the DMail to be fetched. +// +// Returns: +// - model.DMail: A struct containing the DMail data. +// - error: An error, if any, encountered during the API request or response handling. +func GetDmail(requestContext model.RequestContext, ID int) (model.DMail, error) { + // Create a new HTTP GET request to fetch the post information. + r, err := http.NewRequest("GET", fmt.Sprintf("%s/dmails/%d.json", requestContext.Host, ID), nil) + if err != nil { + // Log the error and return an empty Post struct and the error. + + return model.DMail{}, err + } + + r.Header.Set("User-Agent", requestContext.UserAgent) + r.Header.Add("Accept", "application/json") + r.SetBasicAuth(requestContext.Username, requestContext.APIKey) + + // Send the request using the provided http.Client. + resp, err := requestContext.Client.Do(r) + if err != nil { + // Log the error and return an empty Post struct and the error. + + return model.DMail{}, err + } + + // Check if the HTTP response status code indicates success (2xx range). + if resp.StatusCode < 200 || resp.StatusCode > 300 { + // If the status code is outside the 2xx range, return an error based on the status code. + return model.DMail{}, utils.StatusCodesToError(resp.StatusCode) + } + + // Initialize a Post struct to store the response data. + var postResponse model.DMail + + // Decode the JSON response into the PostResponse struct. + err = json.NewDecoder(resp.Body).Decode(&postResponse) + if err != nil { + // Log the error and return an empty Post struct and the error. + + return model.DMail{}, err + } + + // Return the post information and no error (nil). + return postResponse, nil +} + +// DeleteDmail deletes a specific DMail by its ID. +// +// This function performs an HTTP DELETE request to the e621 DMail endpoint. +// +// Parameters: +// - requestContext: The model.RequestContext for the API request. +// - ID: The ID of the DMail to be deleted. +// +// Returns: +// - error: An error, if any, encountered during the API request or response handling. +func DeleteDmail(requestContext model.RequestContext, ID int) error { + // Create a new HTTP GET request to fetch the post information. + r, err := http.NewRequest("DELETE", fmt.Sprintf("%s/dmails/%d.json", requestContext.Host, ID), nil) + if err != nil { + // Log the error and return an empty Post struct and the error. + return err + } + + r.Header.Set("User-Agent", requestContext.UserAgent) + r.Header.Add("Accept", "application/json") + r.SetBasicAuth(requestContext.Username, requestContext.APIKey) + + // Send the request using the provided http.Client. + resp, err := requestContext.Client.Do(r) + if err != nil { + // Log the error and return an empty Post struct and the error. + + return err + } + + // Check if the HTTP response status code indicates success (2xx range). + if resp.StatusCode < 200 || resp.StatusCode > 300 { + // If the status code is outside the 2xx range, return an error based on the status code. + return utils.StatusCodesToError(resp.StatusCode) + } + + // Return the post information and no error (nil). + return nil +} + +// MarkAsReadDmail marks a specific DMail as read by its ID. +// +// This function performs an HTTP PUT request to the e621 DMail endpoint. +// +// Parameters: +// - requestContext: The model.RequestContext for the API request. +// - ID: The ID of the DMail to be marked as read. +// +// Returns: +// - error: An error, if any, encountered during the API request or response handling. +func MarkAsReadDmail(requestContext model.RequestContext, ID int) error { + // Create a new HTTP GET request to fetch the post information. + r, err := http.NewRequest("PUT", fmt.Sprintf("%s/dmails/%d/mark_as_read.json", requestContext.Host, ID), nil) + if err != nil { + // Log the error and return an empty Post struct and the error. + return err + } + + r.Header.Set("User-Agent", requestContext.UserAgent) + r.Header.Add("Accept", "application/json") + r.SetBasicAuth(requestContext.Username, requestContext.APIKey) + + // Send the request using the provided http.Client. + resp, err := requestContext.Client.Do(r) + if err != nil { + // Log the error and return an empty Post struct and the error. + return err + } + + // Check if the HTTP response status code indicates success (2xx range). + if resp.StatusCode < 200 || resp.StatusCode > 300 { + // If the status code is outside the 2xx range, return an error based on the status code. + return utils.StatusCodesToError(resp.StatusCode) + } + + // Return the post information and no error (nil). + return nil +} + +// GetAllDmails retrieves all DMails. +// +// This function performs an HTTP GET request to the e621 DMail endpoint and parses +// the JSON content to extract the DMail data. +// +// Parameters: +// - requestContext: The model.RequestContext for the API request. +// - query: A map containing the query parameters for the request. +// +// Returns: +// - []model.DMail: A slice of structs containing the DMail data. +// - error: An error, if any, encountered during the API request or response handling. +func GetAllDmails(requestContext model.RequestContext, query map[string]string) ([]model.DMail, error) { + // Create a new HTTP GET request. + r, err := http.NewRequest("GET", fmt.Sprintf("%s/dmails.json", requestContext.Host), nil) + if err != nil { + return nil, err + } + + // Append query parameters to the request URL. + q := r.URL.Query() + for k, v := range query { + q.Add(k, v) + } + r.URL.RawQuery = q.Encode() + + r.Header.Set("User-Agent", requestContext.UserAgent) + r.Header.Add("Accept", "application/json") + r.SetBasicAuth(requestContext.Username, requestContext.APIKey) + + // Send the request using the provided http.Client. + resp, err := requestContext.Client.Do(r) + if err != nil { + return nil, err + } + + // Check if the HTTP response status code indicates success (2xx range). + if resp.StatusCode < 200 || resp.StatusCode > 300 { + // If the status code is outside the 2xx range, return an error based on the status code. + return nil, utils.StatusCodesToError(resp.StatusCode) + } + + // Initialize a slice of Post struct to store the response data. + var postResponse []model.DMail + + // Decode the JSON response into the PostResponse struct. + err = json.NewDecoder(resp.Body).Decode(&postResponse) + if err != nil { + // Log the error and return an empty slice and the error. + + return []model.DMail{}, nil + } + + // Return the list of posts and no error (nil). + return postResponse, nil +} + +// MarkAsReadAllDmails marks all DMails as read. +// +// This function performs an HTTP PUT request to the e621 DMail endpoint. +// +// Parameters: +// - requestContext: The model.RequestContext for the API request. +// +// Returns: +// - error: An error, if any, encountered during the API request or response handling. +func MarkAsReadAllDmails(requestContext model.RequestContext) error { + // Create a new HTTP GET request to fetch the post information. + r, err := http.NewRequest("PUT", fmt.Sprintf("%s/dmails/mark_all_as_read.json", requestContext.Host), nil) + + r.Header.Set("User-Agent", requestContext.UserAgent) + r.Header.Add("Accept", "application/json") + r.SetBasicAuth(requestContext.Username, requestContext.APIKey) + + // Send the request using the provided http.Client. + resp, err := requestContext.Client.Do(r) + if err != nil { + // Log the error and return an empty Post struct and the error. + return err + } + + // Check if the HTTP response status code indicates success (2xx range). + if resp.StatusCode < 200 || resp.StatusCode > 300 { + // If the status code is outside the 2xx range, return an error based on the status code. + return utils.StatusCodesToError(resp.StatusCode) + } + + // Return the post information and no error (nil). + return nil +} diff --git a/pkg/e621/endpoints/favorite.go b/pkg/e621/endpoints/favorite.go new file mode 100644 index 0000000..ef9306a --- /dev/null +++ b/pkg/e621/endpoints/favorite.go @@ -0,0 +1,64 @@ +package endpoints + +import ( + "encoding/json" + "fmt" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/utils" + "net/http" +) + +// GetFavorites retrieves a user's favorite posts from the e621 API. +// +// The user_id parameter is required to get the favorites of a user. +// +// Parameters: +// - requestContext: The context for the API request, including the host, user agent, username, and API key. +// - query: A map containing additional query parameters for the API request. +// +// Returns: +// - []model.Post: A slice of favorite posts. +// - error: An error, if any, encountered during the API request or response handling. +func GetFavorites(requestContext model.RequestContext, query map[string]string) ([]model.Post, error) { + // Create a new HTTP GET request. + r, err := http.NewRequest("GET", fmt.Sprintf("%s/favorites.json", requestContext.Host), nil) + if err != nil { + return nil, err + } + + // Append query parameters to the request URL. + q := r.URL.Query() + for k, v := range query { + q.Add(k, v) + } + r.URL.RawQuery = q.Encode() + + r.Header.Set("User-Agent", requestContext.UserAgent) + r.Header.Add("Accept", "application/json") + r.SetBasicAuth(requestContext.Username, requestContext.APIKey) + + // Send the request using the provided http.Client. + resp, err := requestContext.Client.Do(r) + if err != nil { + return nil, err + } + + // Check if the HTTP response status code indicates success (2xx range). + if resp.StatusCode < 200 || resp.StatusCode > 300 { + // If the status code is outside the 2xx range, return an error based on the status code. + return nil, utils.StatusCodesToError(resp.StatusCode) + } + + // Initialize a User struct to store the response data. + var favoriteResponse model.PostResponse + + // Decode the JSON response into the user struct. + err = json.NewDecoder(resp.Body).Decode(&favoriteResponse) + if err != nil { + // Log the error and return an empty User struct and the error. + + return nil, err + } + + return favoriteResponse.Posts, nil +} diff --git a/pkg/e621/endpoints/favorite_test.go b/pkg/e621/endpoints/favorite_test.go new file mode 100644 index 0000000..7af8a5a --- /dev/null +++ b/pkg/e621/endpoints/favorite_test.go @@ -0,0 +1,48 @@ +package endpoints + +import ( + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/utils" + "github.com/jarcoal/httpmock" + "net/http" + "testing" +) + +func TestGetFavorites(t *testing.T) { + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + response, err := utils.LoadJsonTestData[model.PostResponse]("../../../tests/posts.json") + if err != nil { + t.Error(err) + return + } + + responder, err := httpmock.NewJsonResponder(200, response) + if err != nil { + t.Error(err) + return + } + httpmock.RegisterResponder("GET", "https://e621.net/favorites.json", responder) + + requestContext := model.RequestContext{ + Client: http.Client{}, + Host: "https://e621.net", + UserAgent: "Go-e621-SDK (@username)", + Username: "memo", + APIKey: "123456", + } + + posts, err := GetFavorites(requestContext, map[string]string{}) + if err != nil { + t.Error(err) + return + } + + if posts[0].ID == response.Posts[0].ID && posts[1].File.Md5 == response.Posts[1].File.Md5 && posts[2].File.EXT == response.Posts[2].File.EXT { + return + } + + t.Errorf("Respons did not match mock data:\nOriginal: %v\nMock: %v", posts, response) + +} diff --git a/pkg/e621/endpoints/note.go b/pkg/e621/endpoints/note.go new file mode 100644 index 0000000..c298d27 --- /dev/null +++ b/pkg/e621/endpoints/note.go @@ -0,0 +1,121 @@ +package endpoints + +import ( + "encoding/json" + "fmt" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/utils" + "io" + "net/http" + "strings" +) + +// GetNote retrieves a single note by its ID from the e621 API. +// +// Parameters: +// - requestContext: The context for the API request, including the host, user agent, username, and API key. +// - ID: The ID of the note to retrieve. +// +// Returns: +// - model.Note: The retrieved note. +// - error: An error, if any, encountered during the API request or response handling. +func GetNote(requestContext model.RequestContext, ID string) (model.Note, error) { + // Create a new HTTP GET request to fetch the note information. + r, err := http.NewRequest("GET", fmt.Sprintf("%s/notes/%s.json", requestContext.Host, ID), nil) + if err != nil { + // Log the error and return an empty Note struct and the error. + + return model.Note{}, err + } + + r.Header.Set("User-Agent", requestContext.UserAgent) + r.Header.Add("Accept", "application/json") + r.SetBasicAuth(requestContext.Username, requestContext.APIKey) + + // Send the request using the provided http.Client. + resp, err := requestContext.Client.Do(r) + if err != nil { + // Log the error and return an empty Note struct and the error. + + return model.Note{}, err + } + + // Check if the HTTP response status code indicates success (2xx range). + if resp.StatusCode < 200 || resp.StatusCode > 300 { + // If the status code is outside the 2xx range, return an error based on the status code. + return model.Note{}, utils.StatusCodesToError(resp.StatusCode) + } + + // Initialize a Note struct to store the response data. + var noteResponse model.Note + + // Decode the JSON response into the Note struct. + err = json.NewDecoder(resp.Body).Decode(¬eResponse) + if err != nil { + // Log the error and return an empty Note struct and the error. + + return model.Note{}, err + } + + // Return the note information and no error (nil). + return noteResponse, nil +} + +// GetNotes retrieves a list of notes from the e621 API based on query parameters. +// +// Parameters: +// - requestContext: The context for the API request, including the host, user agent, username, and API key. +// - query: A map containing additional query parameters for the API request. +// +// Returns: +// - []model.Note: A slice of notes. +// - error: An error, if any, encountered during the API request or response handling. +func GetNotes(requestContext model.RequestContext, query map[string]string) ([]model.Note, error) { + // Create a new HTTP GET request. + r, err := http.NewRequest("GET", fmt.Sprintf("%s/notes.json", requestContext.Host), nil) + if err != nil { + return nil, err + } + + // Append query parameters to the request URL. + q := r.URL.Query() + for k, v := range query { + q.Add(k, v) + } + r.URL.RawQuery = q.Encode() + + r.Header.Set("User-Agent", requestContext.UserAgent) + r.Header.Add("Accept", "application/json") + r.SetBasicAuth(requestContext.Username, requestContext.APIKey) + + // Send the request using the provided http.Client. + resp, err := requestContext.Client.Do(r) + if err != nil { + return nil, err + } + + // Check if the HTTP response status code indicates success (2xx range). + if resp.StatusCode < 200 || resp.StatusCode > 300 { + // If the status code is outside the 2xx range, return an error based on the status code. + return nil, utils.StatusCodesToError(resp.StatusCode) + } + + respBodyBytes, err := io.ReadAll(resp.Body) + if strings.Contains(string(respBodyBytes), "{\"notes\":[]}") { + return nil, nil + } + + // Initialize a slice of Note struct to store the response data. + var notesResponse []model.Note + + // Decode the JSON response into the slice of Note structs. + err = json.Unmarshal(respBodyBytes, ¬esResponse) + if err != nil { + // Log the error and return an empty slice and the error. + + return nil, err + } + + // Return the list of notes and no error (nil). + return notesResponse, nil +} diff --git a/pkg/e621/endpoints/note_test.go b/pkg/e621/endpoints/note_test.go new file mode 100644 index 0000000..045292e --- /dev/null +++ b/pkg/e621/endpoints/note_test.go @@ -0,0 +1,87 @@ +package endpoints + +import ( + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/utils" + "github.com/jarcoal/httpmock" + "net/http" + "testing" +) + +func TestGetNote(t *testing.T) { + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + response, err := utils.LoadJsonTestData[model.Note]("../../../tests/note.json") + if err != nil { + t.Error(err) + return + } + + responder, err := httpmock.NewJsonResponder(200, response) + if err != nil { + t.Error(err) + return + } + httpmock.RegisterResponder("GET", "https://e621.net/notes/1337.json", responder) + + requestContext := model.RequestContext{ + Client: http.Client{}, + Host: "https://e621.net", + UserAgent: "Go-e621-SDK (@username)", + Username: "memo", + APIKey: "123456", + } + + pool, err := GetNote(requestContext, "1337") + if err != nil { + t.Error(err) + return + } + + if pool.ID == response.ID && pool.Body == response.Body { + return + } + + t.Errorf("Respons did not match mock data:\nOriginal: %v\nMock: %v", pool, response) + +} + +func TestGetNotes(t *testing.T) { + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + response, err := utils.LoadJsonTestData[[]model.Note]("../../../tests/notes.json") + if err != nil { + t.Error(err) + return + } + + responder, err := httpmock.NewJsonResponder(200, response) + if err != nil { + t.Error(err) + return + } + httpmock.RegisterResponder("GET", "https://e621.net/notes.json", responder) + + requestContext := model.RequestContext{ + Client: http.Client{}, + Host: "https://e621.net", + UserAgent: "Go-e621-SDK (@username)", + Username: "memo", + APIKey: "123456", + } + + pools, err := GetNotes(requestContext, map[string]string{}) + if err != nil { + t.Error(err) + return + } + + if pools[0].ID == response[0].ID && pools[1].Body == response[1].Body && pools[2].UpdatedAt == response[2].UpdatedAt { + return + } + + t.Errorf("Respons did not match mock data:\nOriginal: %v\nMock: %v", pools, response) + +} diff --git a/pkg/e621/endpoints/pool.go b/pkg/e621/endpoints/pool.go new file mode 100644 index 0000000..35697d1 --- /dev/null +++ b/pkg/e621/endpoints/pool.go @@ -0,0 +1,114 @@ +package endpoints + +import ( + "encoding/json" + "fmt" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/utils" + "net/http" +) + +// GetPool retrieves a pool by its ID from the e621 API. +// +// Parameters: +// - requestContext: The context for the API request, including the host, user agent, username, and API key. +// - ID: The ID of the pool to retrieve. +// +// Returns: +// - model.Pool: The retrieved pool. +// - error: An error, if any, encountered during the API request or response handling. +func GetPool(requestContext model.RequestContext, ID string) (model.Pool, error) { + // Create a new HTTP GET request to fetch the pool information. + r, err := http.NewRequest("GET", fmt.Sprintf("%s/pools/%s.json", requestContext.Host, ID), nil) + if err != nil { + // Log the error and return an empty Pool struct and the error. + + return model.Pool{}, err + } + + r.Header.Set("User-Agent", requestContext.UserAgent) + r.Header.Add("Accept", "application/json") + r.SetBasicAuth(requestContext.Username, requestContext.APIKey) + + // Send the request using the provided http.Client. + resp, err := requestContext.Client.Do(r) + if err != nil { + // Log the error and return an empty Pool struct and the error. + + return model.Pool{}, err + } + + // Check if the HTTP response status code indicates success (2xx range). + if resp.StatusCode < 200 || resp.StatusCode > 300 { + // If the status code is outside the 2xx range, return an error based on the status code. + return model.Pool{}, utils.StatusCodesToError(resp.StatusCode) + } + + // Initialize a Pool struct to store the response data. + var poolResponse model.Pool + + // Decode the JSON response into the Pool struct. + err = json.NewDecoder(resp.Body).Decode(&poolResponse) + if err != nil { + // Log the error and return an empty Pool struct and the error. + + return model.Pool{}, err + } + + // Return the pool information and no error (nil). + return poolResponse, nil +} + +// GetPools retrieves a list of pools from the e621 API based on query parameters. +// +// Parameters: +// - requestContext: The context for the API request, including the host, user agent, username, and API key. +// - query: A map containing additional query parameters for the API request. +// +// Returns: +// - []model.Pool: A slice of pools. +// - error: An error, if any, encountered during the API request or response handling. +func GetPools(requestContext model.RequestContext, query map[string]string) ([]model.Pool, error) { + // Create a new HTTP GET request. + r, err := http.NewRequest("GET", fmt.Sprintf("%s/pools.json", requestContext.Host), nil) + if err != nil { + return nil, err + } + + // Append query parameters to the request URL. + q := r.URL.Query() + for k, v := range query { + q.Add(k, v) + } + r.URL.RawQuery = q.Encode() + + r.Header.Set("User-Agent", requestContext.UserAgent) + r.Header.Add("Accept", "application/json") + r.SetBasicAuth(requestContext.Username, requestContext.APIKey) + + // Send the request using the provided http.Client. + resp, err := requestContext.Client.Do(r) + if err != nil { + return nil, err + } + + // Check if the HTTP response status code indicates success (2xx range). + if resp.StatusCode < 200 || resp.StatusCode > 300 { + // If the status code is outside the 2xx range, return an error based on the status code. + return nil, utils.StatusCodesToError(resp.StatusCode) + } + + // Initialize a slice of Pool struct to store the response data. + var poolsResponse []model.Pool + + // Decode the JSON response into the slice of Pool structs. + err = json.NewDecoder(resp.Body).Decode(&poolsResponse) + if err != nil { + // Log the error and return an empty slice and the error. + + return nil, err + } + + // Return the list of pools and no error (nil). + return poolsResponse, nil +} diff --git a/pkg/e621/endpoints/pool_test.go b/pkg/e621/endpoints/pool_test.go new file mode 100644 index 0000000..fc36ea0 --- /dev/null +++ b/pkg/e621/endpoints/pool_test.go @@ -0,0 +1,87 @@ +package endpoints + +import ( + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/utils" + "github.com/jarcoal/httpmock" + "net/http" + "testing" +) + +func TestGetPool(t *testing.T) { + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + response, err := utils.LoadJsonTestData[model.Pool]("../../../tests/pool.json") + if err != nil { + t.Error(err) + return + } + + responder, err := httpmock.NewJsonResponder(200, response) + if err != nil { + t.Error(err) + return + } + httpmock.RegisterResponder("GET", "https://e621.net/pools/36957.json", responder) + + requestContext := model.RequestContext{ + Client: http.Client{}, + Host: "https://e621.net", + UserAgent: "Go-e621-SDK (@username)", + Username: "memo", + APIKey: "123456", + } + + pool, err := GetPool(requestContext, "36957") + if err != nil { + t.Error(err) + return + } + + if pool.ID == response.ID && pool.Name == response.Name { + return + } + + t.Errorf("Respons did not match mock data:\nOriginal: %v\nMock: %v", pool, response) + +} + +func TestGetPools(t *testing.T) { + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + response, err := utils.LoadJsonTestData[[]model.Pool]("../../../tests/pools.json") + if err != nil { + t.Error(err) + return + } + + responder, err := httpmock.NewJsonResponder(200, response) + if err != nil { + t.Error(err) + return + } + httpmock.RegisterResponder("GET", "https://e621.net/pools.json", responder) + + requestContext := model.RequestContext{ + Client: http.Client{}, + Host: "https://e621.net", + UserAgent: "Go-e621-SDK (@username)", + Username: "memo", + APIKey: "123456", + } + + pools, err := GetPools(requestContext, map[string]string{}) + if err != nil { + t.Error(err) + return + } + + if pools[0].ID == response[0].ID && pools[1].Name == response[1].Name && pools[2].UpdatedAt == response[2].UpdatedAt { + return + } + + t.Errorf("Respons did not match mock data:\nOriginal: %v\nMock: %v", pools, response) + +} diff --git a/pkg/e621/endpoints/post.go b/pkg/e621/endpoints/post.go new file mode 100644 index 0000000..e923cac --- /dev/null +++ b/pkg/e621/endpoints/post.go @@ -0,0 +1,113 @@ +package endpoints + +import ( + "encoding/json" + "fmt" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/utils" + "net/http" +) + +// GetPost retrieves a single post by its ID from the e621 API. +// +// Parameters: // - requestContext: The context for the API request, including the host, user agent, username, and API key. +// - ID: The ID of the post to retrieve. +// +// Returns: +// - model.Post: The retrieved post. +// - error: An error, if any, encountered during the API request or response handling. +func GetPost(requestContext model.RequestContext, ID string) (model.Post, error) { + // Create a new HTTP GET request to fetch the post information. + r, err := http.NewRequest("GET", fmt.Sprintf("%s/posts/%s.json", requestContext.Host, ID), nil) + if err != nil { + // Log the error and return an empty Post struct and the error. + + return model.Post{}, err + } + + r.Header.Set("User-Agent", requestContext.UserAgent) + r.Header.Add("Accept", "application/json") + r.SetBasicAuth(requestContext.Username, requestContext.APIKey) + + // Send the request using the provided http.Client. + resp, err := requestContext.Client.Do(r) + if err != nil { + // Log the error and return an empty Post struct and the error. + + return model.Post{}, err + } + + // Check if the HTTP response status code indicates success (2xx range). + if resp.StatusCode < 200 || resp.StatusCode > 300 { + // If the status code is outside the 2xx range, return an error based on the status code. + return model.Post{}, utils.StatusCodesToError(resp.StatusCode) + } + + // Initialize a Post struct to store the response data. + var postResponse model.PostResponse + + // Decode the JSON response into the PostResponse struct. + err = json.NewDecoder(resp.Body).Decode(&postResponse) + if err != nil { + // Log the error and return an empty Post struct and the error. + + return model.Post{}, err + } + + // Return the post information and no error (nil). + return postResponse.Post, nil +} + +// GetPosts retrieves a list of posts from the e621 API based on query parameters. +// +// Parameters: +// - requestContext: The context for the API request, including the host, user agent, username, and API key. +// - query: A map containing additional query parameters for the API request. +// +// Returns: +// - []model.Post: A slice of posts. +// - error: An error, if any, encountered during the API request or response handling. +func GetPosts(requestContext model.RequestContext, query map[string]string) ([]model.Post, error) { + // Create a new HTTP GET request. + r, err := http.NewRequest("GET", fmt.Sprintf("%s/posts.json", requestContext.Host), nil) + if err != nil { + return nil, err + } + + // Append query parameters to the request URL. + q := r.URL.Query() + for k, v := range query { + q.Add(k, v) + } + r.URL.RawQuery = q.Encode() + + r.Header.Set("User-Agent", requestContext.UserAgent) + r.Header.Add("Accept", "application/json") + r.SetBasicAuth(requestContext.Username, requestContext.APIKey) + + // Send the request using the provided http.Client. + resp, err := requestContext.Client.Do(r) + if err != nil { + return nil, err + } + + // Check if the HTTP response status code indicates success (2xx range). + if resp.StatusCode < 200 || resp.StatusCode > 300 { + // If the status code is outside the 2xx range, return an error based on the status code. + return nil, utils.StatusCodesToError(resp.StatusCode) + } + + // Initialize a slice of Post struct to store the response data. + var postResponse model.PostResponse + + // Decode the JSON response into the PostResponse struct. + err = json.NewDecoder(resp.Body).Decode(&postResponse) + if err != nil { + // Log the error and return an empty slice and the error. + + return nil, err + } + + // Return the list of posts and no error (nil). + return postResponse.Posts, nil +} diff --git a/pkg/e621/endpoints/post_test.go b/pkg/e621/endpoints/post_test.go new file mode 100644 index 0000000..ca6fa35 --- /dev/null +++ b/pkg/e621/endpoints/post_test.go @@ -0,0 +1,92 @@ +package endpoints + +import ( + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/utils" + "github.com/jarcoal/httpmock" + "net/http" + "testing" +) + +func TestGetPost(t *testing.T) { + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + data, err := utils.LoadJsonTestData[model.Post]("../../../tests/post.json") + if err != nil { + t.Error(err) + return + } + + response := model.PostResponse{ + Post: data, + Posts: nil, + } + + responder, err := httpmock.NewJsonResponder(200, response) + if err != nil { + t.Error(err) + return + } + httpmock.RegisterResponder("GET", "https://e621.net/posts/1337.json", responder) + + requestContext := model.RequestContext{ + Client: http.Client{}, + Host: "https://e621.net", + UserAgent: "Go-e621-SDK (@username)", + Username: "memo", + APIKey: "123456", + } + + post, err := GetPost(requestContext, "1337") + if err != nil { + t.Error(err) + return + } + + if post.ID == response.Post.ID && post.File.URL == response.Post.File.URL { + return + } + + t.Errorf("Respons did not match mock data:\nOriginal: %v\nMock: %v", post, response) + +} + +func TestGetPosts(t *testing.T) { + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + response, err := utils.LoadJsonTestData[model.PostResponse]("../../../tests/posts.json") + if err != nil { + t.Error(err) + return + } + + responder, err := httpmock.NewJsonResponder(200, response) + if err != nil { + t.Error(err) + return + } + httpmock.RegisterResponder("GET", "https://e621.net/posts.json", responder) + + requestContext := model.RequestContext{ + Client: http.Client{}, + Host: "https://e621.net", + UserAgent: "Go-e621-SDK (@username)", + Username: "memo", + APIKey: "123456", + } + + posts, err := GetPosts(requestContext, map[string]string{}) + if err != nil { + t.Error(err) + return + } + + if posts[0].ID == response.Posts[0].ID && posts[1].File.Md5 == response.Posts[1].File.Md5 && posts[2].File.EXT == response.Posts[2].File.EXT { + return + } + + t.Errorf("Respons did not match mock data:\nOriginal: %v\nMock: %v", posts, response) + +} diff --git a/pkg/e621/endpoints/tag.go b/pkg/e621/endpoints/tag.go new file mode 100644 index 0000000..6483eb9 --- /dev/null +++ b/pkg/e621/endpoints/tag.go @@ -0,0 +1,125 @@ +package endpoints + +import ( + "encoding/json" + "fmt" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/utils" + "net/http" +) + +// GetTag retrieves a tag by its ID from the e621 API. +// +// Parameters: +// - requestContext: The context for the API request, including the host, user agent, username, and API key. +// - ID: The ID of the tag to retrieve. +// +// Returns: +// - model.Tag: The retrieved tag. +// - error: An error, if any, encountered during the API request or response handling. +func GetTag(requestContext model.RequestContext, ID string) (model.Tag, error) { + // Create a new HTTP GET request to fetch tag information. + r, err := http.NewRequest("GET", fmt.Sprintf("%s/tags/%s.json", requestContext.Host, ID), nil) + if err != nil { + // Log the error and return an empty Tag struct and the error. + + return model.Tag{}, err + } + + r.Header.Set("User-Agent", requestContext.UserAgent) + r.Header.Add("Accept", "application.json") + r.SetBasicAuth(requestContext.Username, requestContext.APIKey) + + // Send the request using the provided http.Client. + resp, err := requestContext.Client.Do(r) + if err != nil { + // Log the error and return an empty Tag struct and the error. + + return model.Tag{}, err + } + + // Check if the HTTP response status code indicates success (2xx range). + if resp.StatusCode < 200 || resp.StatusCode > 300 { + // If the status code is outside the 2xx range, return an error based on the status code. + return model.Tag{}, utils.StatusCodesToError(resp.StatusCode) + } + + // Initialize a Tag struct to store the response data. + var tag model.Tag + + // Decode the JSON response into the Tag struct. + err = json.NewDecoder(resp.Body).Decode(&tag) + if err != nil { + // Log the error and return an empty Tag struct and the error. + + return model.Tag{}, err + } + + // Return the tag information and no error (nil). + return tag, nil + +} + +// GetTags retrieves a list of tags from the e621 API based on query parameters. +// +// Parameters: +// - requestContext: The context for the API request, including the host, user agent, username, and API key. +// - query: A map containing additional query parameters for the API request. +// +// Query: +// - search[name_matches]: A tag name expression to match against, which can include * as a wildcard. +// - search[category]: Filters results to a particular category. Default value is blank (show all tags). 0 general; 1 artist; 3 copyright; 4 character; 5 species; 6 invalid; 7 meta; 8 lore +// - search[order]: Changes the sort order. Pass one of date (default), count, or name. +// - search[hide_empty]: Hide tags with zero visible posts. Pass true (default) or false. +// - search[has_wiki]: Show only tags with, or without, a wiki page. Pass true, false, or blank (default). +// - search[has_artist]: Show only tags with, or without an artist page. Pass true, false, or blank (default). +// - limit: Maximum number of results to return per query. Default is 75. There is a hard upper limit of 320. +// - page: The page that will be returned. Can also be used with a or b + tag_id to get the tags after or before the specified tag ID. For example a13 gets every tag after tag_id 13 up to the limit. This overrides the specified search ordering, date is always used instead. +// +// Returns: +// - []model.Tag: A slice of tags. +// - error: An error, if any, encountered during the API request or response handling. +func GetTags(requestContext model.RequestContext, query map[string]string) ([]model.Tag, error) { + // Create a new HTTP GET request. + r, err := http.NewRequest("GET", fmt.Sprintf("%s/tags.json", requestContext.Host), nil) + if err != nil { + return nil, err + } + + // Append query parameters to the request URL. + q := r.URL.Query() + for k, v := range query { + q.Add(k, v) + } + r.URL.RawQuery = q.Encode() + + r.Header.Set("User-Agent", requestContext.UserAgent) + r.Header.Add("Accept", "application/json") + r.SetBasicAuth(requestContext.Username, requestContext.APIKey) + + // Send the request using the provided http.Client. + resp, err := requestContext.Client.Do(r) + if err != nil { + return nil, err + } + + // Check if the HTTP response status code indicates success (2xx range). + if resp.StatusCode < 200 || resp.StatusCode > 300 { + // If the status code is outside the 2xx range, return an error based on the status code. + return []model.Tag{}, utils.StatusCodesToError(resp.StatusCode) + } + + // Initialize a slice of Tag struct to store the response data. + var tags []model.Tag + + // Decode the JSON response into the slice of Tag structs. + err = json.NewDecoder(resp.Body).Decode(&tags) + if err != nil { + // Log the error and return an empty slice and the error. + + return []model.Tag{}, err + } + + // Return the list of tags and no error (nil). + return tags, nil +} diff --git a/pkg/e621/endpoints/tag_test.go b/pkg/e621/endpoints/tag_test.go new file mode 100644 index 0000000..bd151c0 --- /dev/null +++ b/pkg/e621/endpoints/tag_test.go @@ -0,0 +1,48 @@ +package endpoints + +import ( + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/utils" + "github.com/jarcoal/httpmock" + "net/http" + "testing" +) + +func TestGetTag(t *testing.T) { + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + response, err := utils.LoadJsonTestData[model.Tag]("../../../tests/tag.json") + if err != nil { + t.Error(err) + return + } + + responder, err := httpmock.NewJsonResponder(200, response) + if err != nil { + t.Error(err) + return + } + httpmock.RegisterResponder("GET", "https://e621.net/tags/165165.json", responder) + + requestContext := model.RequestContext{ + Client: http.Client{}, + Host: "https://e621.net", + UserAgent: "Go-e621-SDK (@username)", + Username: "memo", + APIKey: "123456", + } + + tag, err := GetTag(requestContext, "165165") + if err != nil { + t.Error(err) + return + } + + if tag.ID == response.ID && tag.Name == response.Name && tag.CreatedAt == response.CreatedAt { + return + } + + t.Errorf("Respons did not match mock data:\nOriginal: %v\nMock: %v", tag, response) + +} diff --git a/pkg/e621/endpoints/user.go b/pkg/e621/endpoints/user.go new file mode 100644 index 0000000..f3571a2 --- /dev/null +++ b/pkg/e621/endpoints/user.go @@ -0,0 +1,114 @@ +package endpoints + +import ( + "encoding/json" + "fmt" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/utils" + "net/http" +) + +// GetUser retrieves user information from e621.net based on the provided username. +// +// Parameters: +// - requestContext: The context for the API request, including the host, user agent, username, and API key. +// - username: The username of the user to retrieve. +// +// Returns: +// - model.User: The retrieved user. +// - error: An error, if any, encountered during the API request or response handling. +func GetUser(requestContext model.RequestContext, username string) (model.User, error) { + // Create a new HTTP GET request to fetch user information from the specified 'host' and 'username'. + r, err := http.NewRequest("GET", fmt.Sprintf("%s/users/%s.json", requestContext.Host, username), nil) + if err != nil { + // Log the error and return an empty User struct and the error. + + return model.User{}, err + } + + r.Header.Set("User-Agent", requestContext.UserAgent) + r.Header.Add("Accept", "application/json") + r.SetBasicAuth(requestContext.Username, requestContext.APIKey) + + // Send the request using the provided http.Client. + resp, err := requestContext.Client.Do(r) + if err != nil { + // Log the error and return an empty User struct and the error. + + return model.User{}, err + } + + // Check if the HTTP response status code indicates success (2xx range). + if resp.StatusCode < 200 || resp.StatusCode > 300 { + // If the status code is outside the 2xx range, return an error based on the status code. + return model.User{}, utils.StatusCodesToError(resp.StatusCode) + } + + // Initialize a User struct to store the response data. + var user model.User + + // Decode the JSON response into the User struct. + err = json.NewDecoder(resp.Body).Decode(&user) + if err != nil { + // Log the error and return an empty User struct and the error. + + return model.User{}, err + } + + // Return the user information and no error (nil). + return user, nil +} + +// GetUsers retrieves a list of users from e621.net based on query parameters. +// +// Parameters: +// - requestContext: The context for the API request, including the host, user agent, username, and API key. +// - query: A map containing additional query parameters for the API request. +// +// Returns: +// - []model.User: A slice of users. +// - error: An error, if any, encountered during the API request or response handling. +func GetUsers(requestContext model.RequestContext, query map[string]string) ([]model.User, error) { + // Create a new HTTP GET request. + r, err := http.NewRequest("GET", fmt.Sprintf("%s/users.json", requestContext.Host), nil) + if err != nil { + return nil, err + } + + // Append query parameters to the request URL. + q := r.URL.Query() + for k, v := range query { + q.Add(k, v) + } + r.URL.RawQuery = q.Encode() + + r.Header.Set("User-Agent", requestContext.UserAgent) + r.Header.Add("Accept", "application/json") + r.SetBasicAuth(requestContext.Username, requestContext.APIKey) + + // Send the request using the provided http.Client. + resp, err := requestContext.Client.Do(r) + if err != nil { + return nil, err + } + + // Check if the HTTP response status code indicates success (2xx range). + if resp.StatusCode < 200 || resp.StatusCode > 300 { + // If the status code is outside the 2xx range, return an error based on the status code. + return []model.User{}, utils.StatusCodesToError(resp.StatusCode) + } + + // Initialize a slice of User struct to store the response data. + var users []model.User + + // Decode the JSON response into the slice of User structs. + err = json.NewDecoder(resp.Body).Decode(&users) + if err != nil { + // Log the error and return an empty slice and the error. + + return []model.User{}, err + } + + // Return the list of users and no error (nil). + return users, nil +} diff --git a/pkg/e621/endpoints/user_test.go b/pkg/e621/endpoints/user_test.go new file mode 100644 index 0000000..45950a6 --- /dev/null +++ b/pkg/e621/endpoints/user_test.go @@ -0,0 +1,87 @@ +package endpoints + +import ( + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model" + "git.dragse.it/anthrove/e621-sdk-go/pkg/e621/utils" + "github.com/jarcoal/httpmock" + "net/http" + "testing" +) + +func TestGetUser(t *testing.T) { + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + response, err := utils.LoadJsonTestData[model.User]("../../../tests/user.json") + if err != nil { + t.Error(err) + return + } + + responder, err := httpmock.NewJsonResponder(200, response) + if err != nil { + t.Error(err) + return + } + httpmock.RegisterResponder("GET", "https://e621.net/users/selloo.json", responder) + + requestContext := model.RequestContext{ + Client: http.Client{}, + Host: "https://e621.net", + UserAgent: "Go-e621-SDK (@username)", + Username: "memo", + APIKey: "123456", + } + + user, err := GetUser(requestContext, "selloo") + if err != nil { + t.Error(err) + return + } + + if user.ID == response.ID && user.Name == response.Name && user.CreatedAt == response.CreatedAt { + return + } + + t.Errorf("Respons did not match mock data:\nOriginal: %v\nMock: %v", user, response) + +} + +func TestGetUsers(t *testing.T) { + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + response, err := utils.LoadJsonTestData[[]model.User]("../../../tests/users.json") + if err != nil { + t.Error(err) + return + } + + responder, err := httpmock.NewJsonResponder(200, response) + if err != nil { + t.Error(err) + return + } + httpmock.RegisterResponder("GET", "https://e621.net/users.json", responder) + + requestContext := model.RequestContext{ + Client: http.Client{}, + Host: "https://e621.net", + UserAgent: "Go-e621-SDK (@username)", + Username: "memo", + APIKey: "123456", + } + + user, err := GetUsers(requestContext, map[string]string{}) + if err != nil { + t.Error(err) + return + } + + if user[0].ID == response[0].ID && user[1].Name == response[1].Name { + return + } + + t.Errorf("Respons did not match mock data:\nOriginal: %v\nMock: %v", user, response) + +} diff --git a/pkg/e621/model/basic.go b/pkg/e621/model/basic.go new file mode 100644 index 0000000..1b01c9d --- /dev/null +++ b/pkg/e621/model/basic.go @@ -0,0 +1,15 @@ +package model + +import ( + "golang.org/x/time/rate" + "net/http" +) + +type RequestContext struct { + Client http.Client + RateLimiter *rate.Limiter + Host string + UserAgent string + Username string + APIKey string +} diff --git a/pkg/e621/model/dmail.go b/pkg/e621/model/dmail.go new file mode 100644 index 0000000..294b99d --- /dev/null +++ b/pkg/e621/model/dmail.go @@ -0,0 +1,16 @@ +package model + +import "time" + +type DMail struct { + Body string `json:"body"` + CreatedAt time.Time `json:"created_at"` + FromId int `json:"from_id"` + Id int `json:"id"` + IsDeleted bool `json:"is_deleted"` + IsRead bool `json:"is_read"` + OwnerId int `json:"owner_id"` + Title string `json:"title"` + ToId int `json:"to_id"` + UpdatedAt time.Time `json:"updated_at"` +} diff --git a/pkg/e621/model/note.go b/pkg/e621/model/note.go new file mode 100644 index 0000000..30a2cb0 --- /dev/null +++ b/pkg/e621/model/note.go @@ -0,0 +1,17 @@ +package model + +type Note struct { + ID int64 `json:"id"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` + CreatorID int64 `json:"creator_id"` + X int64 `json:"x"` + Y int64 `json:"y"` + Width int64 `json:"width"` + Height int64 `json:"height"` + Version int64 `json:"version"` + IsActive bool `json:"is_active"` + PostID int64 `json:"post_id"` + Body string `json:"body"` + CreatorName string `json:"creator_name"` +} diff --git a/pkg/e621/model/pool.go b/pkg/e621/model/pool.go new file mode 100644 index 0000000..0d8fff4 --- /dev/null +++ b/pkg/e621/model/pool.go @@ -0,0 +1,30 @@ +package model + +type PoolCategory string +type PoolOrder string + +const ( + Series PoolCategory = "series" + Collection PoolCategory = "collection" +) + +const ( + PoolName PoolOrder = "name" + CreatedAt PoolOrder = "created_at" + UpdatedAt PoolOrder = "updated_at" + PostCount PoolOrder = "post_count" +) + +type Pool struct { + ID int64 `json:"id"` + Name string `json:"name"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` + CreatorID int64 `json:"creator_id"` + Description string `json:"description"` + IsActive bool `json:"is_active"` + Category PoolCategory `json:"category"` + PostIDS []int64 `json:"post_ids"` + CreatorName string `json:"creator_name"` + PostCount int64 `json:"post_count"` +} diff --git a/pkg/e621/model/post.go b/pkg/e621/model/post.go new file mode 100644 index 0000000..c22abb2 --- /dev/null +++ b/pkg/e621/model/post.go @@ -0,0 +1,93 @@ +package model + +type PostID int64 + +type PostResponse struct { + Post Post `json:"post"` + Posts []Post `json:"posts"` +} + +type Post struct { + ID PostID `json:"id"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` + File File `json:"file"` + Preview Preview `json:"preview"` + Sample Sample `json:"sample"` + Score Score `json:"score"` + Tags Tags `json:"tags"` + LockedTags []interface{} `json:"locked_tags"` + ChangeSeq int64 `json:"change_seq"` + Flags Flags `json:"flags"` + Rating string `json:"rating"` + FavCount int64 `json:"fav_count"` + Sources []string `json:"sources"` + Pools []interface{} `json:"pools"` + Relationships Relationships `json:"relationships"` + ApproverID interface{} `json:"approver_id"` + UploaderID int64 `json:"uploader_id"` + Description string `json:"description"` + CommentCount int64 `json:"comment_count"` + IsFavorited bool `json:"is_favorited"` + HasNotes bool `json:"has_notes"` + Duration interface{} `json:"duration"` +} + +type File struct { + Width int64 `json:"width"` + Height int64 `json:"height"` + EXT string `json:"ext"` + Size int64 `json:"size"` + Md5 string `json:"md5"` + URL string `json:"url"` +} + +type Flags struct { + Pending bool `json:"pending"` + Flagged bool `json:"flagged"` + NoteLocked bool `json:"note_locked"` + StatusLocked bool `json:"status_locked"` + RatingLocked bool `json:"rating_locked"` + Deleted bool `json:"deleted"` +} + +type Preview struct { + Width int64 `json:"width"` + Height int64 `json:"height"` + URL string `json:"url"` +} + +type Relationships struct { + ParentID interface{} `json:"parent_id"` + HasChildren bool `json:"has_children"` + HasActiveChildren bool `json:"has_active_children"` + Children []interface{} `json:"children"` +} + +type Sample struct { + Has bool `json:"has"` + Height int64 `json:"height"` + Width int64 `json:"width"` + URL string `json:"url"` + Alternates Alternates `json:"alternates"` +} + +type Alternates struct { +} + +type Score struct { + Up int64 `json:"up"` + Down int64 `json:"down"` + Total int64 `json:"total"` +} + +type Tags struct { + General []string `json:"general"` + Artist []string `json:"artist"` + Copyright []string `json:"copyright"` + Character []string `json:"character"` + Species []string `json:"species"` + Invalid []string `json:"invalid"` + Meta []string `json:"meta"` + Lore []string `json:"lore"` +} diff --git a/pkg/e621/model/tag.go b/pkg/e621/model/tag.go new file mode 100644 index 0000000..9851807 --- /dev/null +++ b/pkg/e621/model/tag.go @@ -0,0 +1,26 @@ +package model + +type TagCategory int + +const ( + General TagCategory = iota + Artist + Copyright TagCategory = iota + 1 + Character + Species + Invalide + Meta + Lore +) + +type Tag struct { + ID int64 `json:"id"` + Name string `json:"name"` + PostCount int64 `json:"post_count"` + RelatedTags string `json:"related_tags"` + RelatedTagsUpdatedAt string `json:"related_tags_updated_at"` + Category TagCategory `json:"category"` + IsLocked bool `json:"is_locked"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` +} diff --git a/pkg/e621/model/user.go b/pkg/e621/model/user.go new file mode 100644 index 0000000..2a915cf --- /dev/null +++ b/pkg/e621/model/user.go @@ -0,0 +1,51 @@ +package model + +type UserID int64 +type UserLevel int +type Order string + +const ( + Anonymus UserLevel = 0 + Blocked UserLevel = 10 + Member UserLevel = 20 + Privilaged UserLevel = 30 + FormerStaff UserLevel = 34 + Janitor UserLevel = 35 + Moderator UserLevel = 40 + Admin UserLevel = 50 +) + +const ( + JoinDate Order = "date" + UserName Order = "name" + PostUploadCount Order = "post_upload_count" + NoteCount Order = "note_count" + PostUpdateCount Order = "post_upload_count" +) + +type User struct { + WikiPageVersionCount int64 `json:"wiki_page_version_count"` + ArtistVersionCount int64 `json:"artist_version_count"` + PoolVersionCount int64 `json:"pool_version_count"` + ForumPostCount int64 `json:"forum_post_count"` + CommentCount int64 `json:"comment_count"` + FlagCount int64 `json:"flag_count"` + FavoriteCount int64 `json:"favorite_count"` + PositiveFeedbackCount int64 `json:"positive_feedback_count"` + NeutralFeedbackCount int64 `json:"neutral_feedback_count"` + NegativeFeedbackCount int64 `json:"negative_feedback_count"` + UploadLimit int64 `json:"upload_limit"` + ID UserID `json:"id"` + CreatedAt string `json:"created_at"` + Name string `json:"name"` + Level UserLevel `json:"level"` + BaseUploadLimit int64 `json:"base_upload_limit"` + PostUploadCount int64 `json:"post_upload_count"` + PostUpdateCount int64 `json:"post_update_count"` + NoteUpdateCount int64 `json:"note_update_count"` + IsBanned bool `json:"is_banned"` + CanApprovePosts bool `json:"can_approve_posts"` + CanUploadFree bool `json:"can_upload_free"` + LevelString string `json:"level_string"` + AvatarID PostID `json:"avatar_id"` +} diff --git a/pkg/e621/utils/constants.go b/pkg/e621/utils/constants.go new file mode 100644 index 0000000..d7df2e8 --- /dev/null +++ b/pkg/e621/utils/constants.go @@ -0,0 +1,4 @@ +package utils + +// E621_MAX_POST_COUNT is the maximum allowable post count for E621. +const E621_MAX_POST_COUNT = 320 diff --git a/pkg/e621/utils/error.go b/pkg/e621/utils/error.go new file mode 100644 index 0000000..d12b897 --- /dev/null +++ b/pkg/e621/utils/error.go @@ -0,0 +1,106 @@ +package utils + +import "fmt" + +// StatusCodesToError maps HTTP status codes to corresponding error types. +func StatusCodesToError(statusCode int) error { + var err error + switch statusCode { + case 403: + err = AccessDeniedError{} + case 404: + err = NotFoundError{} + case 412: + err = PreconditionFailedError{} + case 421: + err = RateLimitReachedError{} + case 424: + err = InvalidParametersError{} + case 500: + err = InternalServerError{} + case 502: + err = BadGatewayError{} + case 503: + err = ServiceUnavailableError{} + default: + err = fmt.Errorf("unhandled status code: %d", statusCode) + } + return err +} + +// AccessDeniedError represents an "Access Denied" error. +type AccessDeniedError struct{} + +func (_ AccessDeniedError) Error() string { + return "access denied" +} + +// NotFoundError represents a "Not Found" error. +type NotFoundError struct{} + +func (_ NotFoundError) Error() string { + return "not found" +} + +// PreconditionFailedError represents a "Precondition Failed" error. +type PreconditionFailedError struct{} + +func (_ PreconditionFailedError) Error() string { + return "precondition failed" +} + +// RateLimitReachedError represents a "Rate Limit Reached" error. +type RateLimitReachedError struct{} + +func (_ RateLimitReachedError) Error() string { + return "rate limit reached" +} + +// InvalidParametersError represents an "Invalid Parameters" error. +type InvalidParametersError struct{} + +func (_ InvalidParametersError) Error() string { + return "invalid parameters" +} + +// InternalServerError represents an "Internal Server Error" error. +type InternalServerError struct{} + +func (_ InternalServerError) Error() string { + return "internal server error" +} + +// BadGatewayError represents a "Bad Gateway" error. +type BadGatewayError struct{} + +func (_ BadGatewayError) Error() string { + return "bad gateway" +} + +// ServiceUnavailableError represents a "Service Unavailable" error. +type ServiceUnavailableError struct{} + +func (_ ServiceUnavailableError) Error() string { + return "service unavailable" +} + +// UnknownError represents an "Unknown" error. +type UnknownError struct{} + +func (_ UnknownError) Error() string { + return "unknown error" +} + +// OriginConnectionTimeOutError represents an "Origin Connection Time-Out" error. +type OriginConnectionTimeOutError struct{} + +func (_ OriginConnectionTimeOutError) Error() string { + return "origin connection time-out" +} + +// SSLHandshakeFailedError represents an "SSL Handshake Failed" error. +type SSLHandshakeFailedError struct{} + +func (_ SSLHandshakeFailedError) Error() string { + return "ssl handshake failed" +} diff --git a/pkg/e621/utils/test.go b/pkg/e621/utils/test.go new file mode 100644 index 0000000..39a9a71 --- /dev/null +++ b/pkg/e621/utils/test.go @@ -0,0 +1,28 @@ +package utils + +import ( + "encoding/json" + "os" +) + +func LoadJsonTestData[T any](testDataPath string) (T, error) { + // Create a variable to store the decoded JSON data + var jsonData T + + // Open the JSON file + file, err := os.Open(testDataPath) + if err != nil { + return jsonData, err + } + defer file.Close() + + // Create a decoder + decoder := json.NewDecoder(file) + + // Decode the JSON data into the struct + if err := decoder.Decode(&jsonData); err != nil { + return jsonData, err + } + + return jsonData, nil +} diff --git a/tests/note.json b/tests/note.json new file mode 100644 index 0000000..60b5c4b --- /dev/null +++ b/tests/note.json @@ -0,0 +1,15 @@ +{ + "id": 1337, + "created_at": "2009-05-19T13:30:28.360-04:00", + "updated_at": "2009-06-02T16:52:06.418-04:00", + "creator_id": 2942, + "x": 163, + "y": 718, + "width": 99, + "height": 47, + "version": 2, + "is_active": false, + "post_id": 31570, + "body": "BORANGE MORE LIKE IT", + "creator_name": "DahWuffie" +} \ No newline at end of file diff --git a/tests/notes.json b/tests/notes.json new file mode 100644 index 0000000..a75bb1b --- /dev/null +++ b/tests/notes.json @@ -0,0 +1,1127 @@ +[ + { + "id": 386349, + "created_at": "2023-10-24T09:35:18.436-04:00", + "updated_at": "2023-10-24T09:35:18.436-04:00", + "creator_id": 1006937, + "x": 79, + "y": 2961, + "width": 476, + "height": 682, + "version": 2, + "is_active": true, + "post_id": 4371092, + "body": "What's wrong, Raven?!", + "creator_name": "S0-pra-N0" + }, + { + "id": 386348, + "created_at": "2023-10-24T09:34:41.014-04:00", + "updated_at": "2023-10-24T09:34:41.014-04:00", + "creator_id": 1006937, + "x": 1179, + "y": 2732, + "width": 486, + "height": 684, + "version": 2, + "is_active": true, + "post_id": 4371092, + "body": "RAVEN?!", + "creator_name": "S0-pra-N0" + }, + { + "id": 386347, + "created_at": "2023-10-24T09:34:06.274-04:00", + "updated_at": "2023-10-24T09:34:06.274-04:00", + "creator_id": 1006937, + "x": 83, + "y": 1729, + "width": 484, + "height": 690, + "version": 2, + "is_active": true, + "post_id": 4371092, + "body": "It would be nice if you didn't stare at me like that...", + "creator_name": "S0-pra-N0" + }, + { + "id": 386346, + "created_at": "2023-10-24T09:33:04.058-04:00", + "updated_at": "2023-10-24T09:33:04.058-04:00", + "creator_id": 1006937, + "x": 1313, + "y": 1351, + "width": 389, + "height": 599, + "version": 2, + "is_active": true, + "post_id": 4371092, + "body": "How do I look, Raven?", + "creator_name": "S0-pra-N0" + }, + { + "id": 386345, + "created_at": "2023-10-24T09:32:26.529-04:00", + "updated_at": "2023-10-24T09:32:26.529-04:00", + "creator_id": 1006937, + "x": 804, + "y": 684, + "width": 387, + "height": 537, + "version": 2, + "is_active": true, + "post_id": 4371092, + "body": "Let's use our \"contact\"...", + "creator_name": "S0-pra-N0" + }, + { + "id": 386344, + "created_at": "2023-10-24T09:31:19.562-04:00", + "updated_at": "2023-10-24T09:31:19.562-04:00", + "creator_id": 1006937, + "x": 1348, + "y": 255, + "width": 364, + "height": 653, + "version": 2, + "is_active": true, + "post_id": 4371092, + "body": "If you want to see my true appearance...", + "creator_name": "S0-pra-N0" + }, + { + "id": 386343, + "created_at": "2023-10-24T07:49:29.263-04:00", + "updated_at": "2023-10-24T07:49:29.263-04:00", + "creator_id": 1269021, + "x": 974, + "y": 2363, + "width": 61, + "height": 40, + "version": 2, + "is_active": true, + "post_id": 4345287, + "body": "*am", + "creator_name": "pokefan856" + }, + { + "id": 386342, + "created_at": "2023-10-24T07:48:56.892-04:00", + "updated_at": "2023-10-24T07:48:56.892-04:00", + "creator_id": 1269021, + "x": 146, + "y": 2770, + "width": 61, + "height": 46, + "version": 2, + "is_active": true, + "post_id": 4345287, + "body": "*am", + "creator_name": "pokefan856" + }, + { + "id": 386341, + "created_at": "2023-10-24T07:48:23.400-04:00", + "updated_at": "2023-10-24T07:48:23.400-04:00", + "creator_id": 1269021, + "x": 1364, + "y": 1752, + "width": 61, + "height": 33, + "version": 2, + "is_active": true, + "post_id": 4345287, + "body": "*am", + "creator_name": "pokefan856" + }, + { + "id": 386340, + "created_at": "2023-10-24T07:47:56.208-04:00", + "updated_at": "2023-10-24T07:47:56.208-04:00", + "creator_id": 1269021, + "x": 787, + "y": 1537, + "width": 59, + "height": 33, + "version": 2, + "is_active": true, + "post_id": 4345287, + "body": "*am", + "creator_name": "pokefan856" + }, + { + "id": 386339, + "created_at": "2023-10-24T07:47:31.417-04:00", + "updated_at": "2023-10-24T07:47:31.417-04:00", + "creator_id": 1269021, + "x": 1634, + "y": 1191, + "width": 56, + "height": 27, + "version": 2, + "is_active": true, + "post_id": 4345287, + "body": "*am", + "creator_name": "pokefan856" + }, + { + "id": 386338, + "created_at": "2023-10-24T07:00:25.567-04:00", + "updated_at": "2023-10-24T07:00:25.567-04:00", + "creator_id": 747940, + "x": 190, + "y": 742, + "width": 143, + "height": 121, + "version": 2, + "is_active": true, + "post_id": 4369989, + "body": "Whore", + "creator_name": "pleaseletmein" + }, + { + "id": 386337, + "created_at": "2023-10-24T06:05:49.173-04:00", + "updated_at": "2023-10-24T06:05:49.173-04:00", + "creator_id": 197730, + "x": 651, + "y": 92, + "width": 376, + "height": 148, + "version": 2, + "is_active": true, + "post_id": 4369313, + "body": "Since certain patches, Lamb seems to have difficulty standing out in its way of chasing the rift as before", + "creator_name": "bryannashy" + }, + { + "id": 386336, + "created_at": "2023-10-24T06:03:31.201-04:00", + "updated_at": "2023-10-24T06:03:31.201-04:00", + "creator_id": 197730, + "x": 26, + "y": 54, + "width": 589, + "height": 204, + "version": 2, + "is_active": true, + "post_id": 4369313, + "body": "Evelynn certainly being a sadistic demon with nymphomaniac tendencies, she is not a monster either; And was given to Lamb the knowledge to take a few hillbillies to the grave effectively without even getting too wet", + "creator_name": "bryannashy" + }, + { + "id": 386335, + "created_at": "2023-10-24T05:43:43.658-04:00", + "updated_at": "2023-10-24T05:43:43.658-04:00", + "creator_id": 934802, + "x": 82, + "y": 3554, + "width": 2781, + "height": 518, + "version": 2, + "is_active": true, + "post_id": 4370867, + "body": "on the next day at 8 AM, we were able to receive the books safely.\n\nThe driver who delivered the books said to us \"We are the only ones who could've printed the books the same day\" \"We would've been dead with we couldn't do it\". Chills man.\n\nThere were some quality problems due to the unforeseen accidents, but we were informed of this earlier and were prepared for it and agreed upon it,\n\nI would like to take this opportunity to thank the printers for their hard work in a very short amount of time, the contributors for their wonderful manuscripts, the purchasers for their warm words of encouragement when I explained the quality of the book and said \"It's fine\" on the day of the event, and above all, YamatoKuroko-san who never gave up on me, and worked tirelessly to the very end.", + "creator_name": "Benjiboyo" + }, + { + "id": 386334, + "created_at": "2023-10-24T05:32:49.125-04:00", + "updated_at": "2023-10-24T05:32:49.125-04:00", + "creator_id": 934802, + "x": 137, + "y": 1484, + "width": 236, + "height": 1106, + "version": 2, + "is_active": true, + "post_id": 4370867, + "body": "Kuroko-san, i don't feel so good...", + "creator_name": "Benjiboyo" + }, + { + "id": 386333, + "created_at": "2023-10-24T05:31:51.318-04:00", + "updated_at": "2023-10-24T05:31:51.318-04:00", + "creator_id": 934802, + "x": 2491, + "y": 1423, + "width": 261, + "height": 1047, + "version": 2, + "is_active": true, + "post_id": 4370867, + "body": "Looks like we're still not in the clear yet...", + "creator_name": "Benjiboyo" + }, + { + "id": 386332, + "created_at": "2023-10-24T05:30:43.104-04:00", + "updated_at": "2023-10-24T05:30:43.104-04:00", + "creator_id": 934802, + "x": 1314, + "y": 1473, + "width": 1072, + "height": 99, + "version": 2, + "is_active": true, + "post_id": 4370867, + "body": "They were actually very attentive and courteous", + "creator_name": "Benjiboyo" + }, + { + "id": 386331, + "created_at": "2023-10-24T05:29:51.750-04:00", + "updated_at": "2023-10-24T05:29:51.750-04:00", + "creator_id": 934802, + "x": 539, + "y": 243, + "width": 1838, + "height": 1158, + "version": 2, + "is_active": true, + "post_id": 4370867, + "body": "So sorry guys, but our main printer is busted!!\nWe can't make the pick up in 23:00 but we'll try and print it overnight somehow! again sorry!\n\nWe're working as hard as we can, but the printing quality is not as good, this is the extent of what we can do in such a deadline! I hope you can understand!\n\nWe'll deliver them straight to the venue tomorrow morning when we're done binding, you can pick them up there!\n\nOh, and we only accept cash.", + "creator_name": "Benjiboyo" + }, + { + "id": 386330, + "created_at": "2023-10-24T05:24:03.468-04:00", + "updated_at": "2023-10-24T05:24:03.468-04:00", + "creator_id": 934802, + "x": 553, + "y": 37, + "width": 504, + "height": 111, + "version": 2, + "is_active": true, + "post_id": 4370867, + "body": "Super summarized version", + "creator_name": "Benjiboyo" + }, + { + "id": 386329, + "created_at": "2023-10-24T05:22:34.562-04:00", + "updated_at": "2023-10-24T05:22:34.562-04:00", + "creator_id": 934802, + "x": 305, + "y": 2993, + "width": 174, + "height": 181, + "version": 2, + "is_active": true, + "post_id": 4370866, + "body": "*instant aging*", + "creator_name": "Benjiboyo" + }, + { + "id": 386328, + "created_at": "2023-10-24T05:22:15.310-04:00", + "updated_at": "2023-10-24T05:22:15.310-04:00", + "creator_id": 934802, + "x": 2441, + "y": 3366, + "width": 43, + "height": 106, + "version": 2, + "is_active": true, + "post_id": 4370866, + "body": "'scuse me", + "creator_name": "Benjiboyo" + }, + { + "id": 386327, + "created_at": "2023-10-24T05:21:49.996-04:00", + "updated_at": "2023-10-24T05:21:49.996-04:00", + "creator_id": 934802, + "x": 2502, + "y": 2809, + "width": 168, + "height": 722, + "version": 2, + "is_active": true, + "post_id": 4370866, + "body": "Huh, what's up!? What's wrong!?", + "creator_name": "Benjiboyo" + }, + { + "id": 386326, + "created_at": "2023-10-24T05:21:38.667-04:00", + "updated_at": "2023-10-24T05:21:38.667-04:00", + "creator_id": 934802, + "x": 69, + "y": 2043, + "width": 272, + "height": 677, + "version": 2, + "is_active": true, + "post_id": 4370866, + "body": "It's an e-mail from the printing place...\n\nAh...", + "creator_name": "Benjiboyo" + }, + { + "id": 386325, + "created_at": "2023-10-24T05:21:11.875-04:00", + "updated_at": "2023-10-24T05:21:11.875-04:00", + "creator_id": 934802, + "x": 1405, + "y": 1680, + "width": 240, + "height": 556, + "version": 2, + "is_active": true, + "post_id": 4370866, + "body": "Who could be messaging at 11 PM... Oh!", + "creator_name": "Benjiboyo" + }, + { + "id": 386324, + "created_at": "2023-10-24T05:20:47.663-04:00", + "updated_at": "2023-10-24T05:20:47.663-04:00", + "creator_id": 934802, + "x": 2455, + "y": 1689, + "width": 268, + "height": 568, + "version": 2, + "is_active": true, + "post_id": 4370866, + "body": "Well, I look forward to receiving the results", + "creator_name": "Benjiboyo" + }, + { + "id": 386323, + "created_at": "2023-10-24T05:18:47.279-04:00", + "updated_at": "2023-10-24T05:18:47.279-04:00", + "creator_id": 934802, + "x": 73, + "y": 1298, + "width": 104, + "height": 249, + "version": 2, + "is_active": true, + "post_id": 4370866, + "body": "The thought scares me", + "creator_name": "Benjiboyo" + }, + { + "id": 386322, + "created_at": "2023-10-24T05:18:34.815-04:00", + "updated_at": "2023-10-24T05:18:34.815-04:00", + "creator_id": 934802, + "x": 146, + "y": 557, + "width": 199, + "height": 718, + "version": 2, + "is_active": true, + "post_id": 4370866, + "body": "I dunno whad woud've habbened ib you weren't here gurogo-san", + "creator_name": "Benjiboyo" + }, + { + "id": 386321, + "created_at": "2023-10-24T05:17:33.650-04:00", + "updated_at": "2023-10-24T05:17:33.650-04:00", + "creator_id": 934802, + "x": 1248, + "y": 518, + "width": 215, + "height": 804, + "version": 2, + "is_active": true, + "post_id": 4370866, + "body": "dhank you sho muuuuuush", + "creator_name": "Benjiboyo" + }, + { + "id": 386320, + "created_at": "2023-10-24T05:17:16.568-04:00", + "updated_at": "2023-10-24T05:17:16.568-04:00", + "creator_id": 934802, + "x": 2509, + "y": 391, + "width": 270, + "height": 847, + "version": 2, + "is_active": true, + "post_id": 4370866, + "body": "Well-\n\nI'm relieved it all worked out somehow...", + "creator_name": "Benjiboyo" + }, + { + "id": 386319, + "created_at": "2023-10-24T05:16:20.905-04:00", + "updated_at": "2023-10-24T05:16:20.905-04:00", + "creator_id": 934802, + "x": 1712, + "y": 193, + "width": 715, + "height": 134, + "version": 2, + "is_active": true, + "post_id": 4370866, + "body": "And on Kemokett Eve", + "creator_name": "Benjiboyo" + }, + { + "id": 386318, + "created_at": "2023-10-24T05:15:12.143-04:00", + "updated_at": "2023-10-24T05:15:12.143-04:00", + "creator_id": 934802, + "x": 698, + "y": 3841, + "width": 52, + "height": 54, + "version": 2, + "is_active": true, + "post_id": 4370865, + "body": "Print guy", + "creator_name": "Benjiboyo" + }, + { + "id": 386317, + "created_at": "2023-10-24T05:15:08.210-04:00", + "updated_at": "2023-10-24T05:15:08.210-04:00", + "creator_id": 934802, + "x": 1182, + "y": 3759, + "width": 70, + "height": 74, + "version": 2, + "is_active": true, + "post_id": 4370865, + "body": "Print guy", + "creator_name": "Benjiboyo" + }, + { + "id": 386316, + "created_at": "2023-10-24T05:14:57.962-04:00", + "updated_at": "2023-10-24T05:15:52.617-04:00", + "creator_id": 934802, + "x": 2455, + "y": 3916, + "width": 320, + "height": 102, + "version": 3, + "is_active": true, + "post_id": 4370865, + "body": "Submission Complete", + "creator_name": "Benjiboyo" + }, + { + "id": 386315, + "created_at": "2023-10-24T05:14:49.438-04:00", + "updated_at": "2023-10-24T05:14:49.438-04:00", + "creator_id": 934802, + "x": 346, + "y": 3702, + "width": 109, + "height": 299, + "version": 2, + "is_active": true, + "post_id": 4370865, + "body": "HUUUH!?", + "creator_name": "Benjiboyo" + }, + { + "id": 386314, + "created_at": "2023-10-24T05:14:42.711-04:00", + "updated_at": "2023-10-24T05:14:42.711-04:00", + "creator_id": 934802, + "x": 546, + "y": 3386, + "width": 179, + "height": 474, + "version": 2, + "is_active": true, + "post_id": 4370865, + "body": "Ah, there seems to still be a frame you forgot to delete", + "creator_name": "Benjiboyo" + }, + { + "id": 386313, + "created_at": "2023-10-24T05:14:13.909-04:00", + "updated_at": "2023-10-24T05:14:13.909-04:00", + "creator_id": 934802, + "x": 814, + "y": 3616, + "width": 102, + "height": 286, + "version": 2, + "is_active": true, + "post_id": 4370865, + "body": "Understood!!", + "creator_name": "Benjiboyo" + }, + { + "id": 386312, + "created_at": "2023-10-24T05:14:06.311-04:00", + "updated_at": "2023-10-24T05:14:06.311-04:00", + "creator_id": 934802, + "x": 1037, + "y": 3447, + "width": 145, + "height": 299, + "version": 2, + "is_active": true, + "post_id": 4370865, + "body": "Let me confirm the data first", + "creator_name": "Benjiboyo" + }, + { + "id": 386311, + "created_at": "2023-10-24T05:12:16.306-04:00", + "updated_at": "2023-10-24T05:12:16.306-04:00", + "creator_id": 934802, + "x": 246, + "y": 2357, + "width": 159, + "height": 440, + "version": 2, + "is_active": true, + "post_id": 4370865, + "body": "YESSIR!!", + "creator_name": "Benjiboyo" + }, + { + "id": 386310, + "created_at": "2023-10-24T05:12:05.724-04:00", + "updated_at": "2023-10-24T05:12:05.724-04:00", + "creator_id": 934802, + "x": 198, + "y": 543, + "width": 224, + "height": 838, + "version": 2, + "is_active": true, + "post_id": 4370865, + "body": "SEND ME THE URL AND APPLICATIONS!!", + "creator_name": "Benjiboyo" + }, + { + "id": 386309, + "created_at": "2023-10-24T05:11:40.203-04:00", + "updated_at": "2023-10-24T05:11:40.203-04:00", + "creator_id": 934802, + "x": 2530, + "y": 350, + "width": 120, + "height": 795, + "version": 2, + "is_active": true, + "post_id": 4370865, + "body": "FOUND OOOOOOONE!!", + "creator_name": "Benjiboyo" + }, + { + "id": 386308, + "created_at": "2023-10-24T05:11:21.468-04:00", + "updated_at": "2023-10-24T05:11:21.468-04:00", + "creator_id": 934802, + "x": 146, + "y": 3275, + "width": 220, + "height": 808, + "version": 2, + "is_active": true, + "post_id": 4370864, + "body": "h3. ALL RIGHT", + "creator_name": "Benjiboyo" + }, + { + "id": 386307, + "created_at": "2023-10-24T05:11:07.137-04:00", + "updated_at": "2023-10-24T05:11:07.137-04:00", + "creator_id": 934802, + "x": 1264, + "y": 3138, + "width": 131, + "height": 220, + "version": 2, + "is_active": true, + "post_id": 4370864, + "body": "I'm begging", + "creator_name": "Benjiboyo" + }, + { + "id": 386306, + "created_at": "2023-10-24T05:10:59.432-04:00", + "updated_at": "2023-10-24T05:10:59.432-04:00", + "creator_id": 934802, + "x": 1837, + "y": 2950, + "width": 106, + "height": 302, + "version": 2, + "is_active": true, + "post_id": 4370864, + "body": "Please", + "creator_name": "Benjiboyo" + }, + { + "id": 386305, + "created_at": "2023-10-24T05:10:54.446-04:00", + "updated_at": "2023-10-24T05:10:54.446-04:00", + "creator_id": 934802, + "x": 180, + "y": 2014, + "width": 190, + "height": 527, + "version": 2, + "is_active": true, + "post_id": 4370864, + "body": "It's all up to Kuroko-san on finding that print place", + "creator_name": "Benjiboyo" + }, + { + "id": 386304, + "created_at": "2023-10-24T05:10:10.724-04:00", + "updated_at": "2023-10-24T05:10:10.724-04:00", + "creator_id": 934802, + "x": 2482, + "y": 1995, + "width": 84, + "height": 374, + "version": 2, + "is_active": true, + "post_id": 4370864, + "body": "The publisher", + "creator_name": "Benjiboyo" + }, + { + "id": 386303, + "created_at": "2023-10-24T05:08:46.638-04:00", + "updated_at": "2023-10-24T05:08:46.638-04:00", + "creator_id": 934802, + "x": 316, + "y": 748, + "width": 97, + "height": 384, + "version": 2, + "is_active": true, + "post_id": 4370864, + "body": "Finally", + "creator_name": "Benjiboyo" + }, + { + "id": 386302, + "created_at": "2023-10-24T05:08:39.701-04:00", + "updated_at": "2023-10-24T05:08:39.701-04:00", + "creator_id": 934802, + "x": 2318, + "y": 652, + "width": 265, + "height": 593, + "version": 2, + "is_active": true, + "post_id": 4370864, + "body": "All right, both the corrections and the confirmations are OK", + "creator_name": "Benjiboyo" + }, + { + "id": 386301, + "created_at": "2023-10-24T05:06:42.983-04:00", + "updated_at": "2023-10-24T05:06:42.983-04:00", + "creator_id": 479627, + "x": 668, + "y": 368, + "width": 112, + "height": 43, + "version": 2, + "is_active": true, + "post_id": 4370527, + "body": "Heheh, you're so good.", + "creator_name": "Jinn" + }, + { + "id": 386300, + "created_at": "2023-10-24T05:04:20.523-04:00", + "updated_at": "2023-10-24T05:04:20.523-04:00", + "creator_id": 479627, + "x": 580, + "y": 214, + "width": 191, + "height": 80, + "version": 2, + "is_active": true, + "post_id": 4370527, + "body": "Please, Wasp King... please make me lay eggs for the rest of my life...", + "creator_name": "Jinn" + }, + { + "id": 386299, + "created_at": "2023-10-24T04:57:26.047-04:00", + "updated_at": "2023-10-24T04:57:26.047-04:00", + "creator_id": 479627, + "x": 41, + "y": 111, + "width": 223, + "height": 108, + "version": 2, + "is_active": true, + "post_id": 4370527, + "body": "I-I am a pathetic bee cumdump who's in love with my alpha's huge cock...", + "creator_name": "Jinn" + }, + { + "id": 386298, + "created_at": "2023-10-24T04:55:43.201-04:00", + "updated_at": "2023-10-24T04:55:43.201-04:00", + "creator_id": 479627, + "x": 486, + "y": 47, + "width": 257, + "height": 48, + "version": 2, + "is_active": true, + "post_id": 4370527, + "body": "Now, tell me what I've taught you.", + "creator_name": "Jinn" + }, + { + "id": 386297, + "created_at": "2023-10-24T04:30:31.829-04:00", + "updated_at": "2023-10-24T04:30:31.829-04:00", + "creator_id": 479627, + "x": 92, + "y": 409, + "width": 181, + "height": 39, + "version": 2, + "is_active": true, + "post_id": 4370526, + "body": "We'll use you as our seedbed!!", + "creator_name": "Jinn" + }, + { + "id": 386296, + "created_at": "2023-10-24T04:30:12.756-04:00", + "updated_at": "2023-10-24T04:30:12.756-04:00", + "creator_id": 454665, + "x": 31, + "y": 1106, + "width": 221, + "height": 92, + "version": 2, + "is_active": true, + "post_id": 4367223, + "body": "[b]*PLUNGE*[/b]", + "creator_name": "garfieldfromgarfield" + }, + { + "id": 386295, + "created_at": "2023-10-24T04:29:08.921-04:00", + "updated_at": "2023-10-24T04:29:08.921-04:00", + "creator_id": 454665, + "x": 61, + "y": 629, + "width": 211, + "height": 387, + "version": 2, + "is_active": true, + "post_id": 4367223, + "body": "[b]*SHPBLPLBHLHLP*[/b]", + "creator_name": "garfieldfromgarfield" + }, + { + "id": 386294, + "created_at": "2023-10-24T04:27:54.698-04:00", + "updated_at": "2023-10-24T04:27:54.698-04:00", + "creator_id": 454665, + "x": 836, + "y": 454, + "width": 126, + "height": 221, + "version": 2, + "is_active": true, + "post_id": 4367223, + "body": "GHUUHHH...!", + "creator_name": "garfieldfromgarfield" + }, + { + "id": 386293, + "created_at": "2023-10-24T04:27:44.666-04:00", + "updated_at": "2023-10-24T04:27:44.666-04:00", + "creator_id": 454665, + "x": 991, + "y": 348, + "width": 102, + "height": 141, + "version": 2, + "is_active": true, + "post_id": 4367223, + "body": "OGH!", + "creator_name": "garfieldfromgarfield" + }, + { + "id": 386292, + "created_at": "2023-10-24T04:27:32.813-04:00", + "updated_at": "2023-10-24T04:27:32.813-04:00", + "creator_id": 454665, + "x": 1025, + "y": 19, + "width": 111, + "height": 306, + "version": 2, + "is_active": true, + "post_id": 4367223, + "body": "Ahh! ♥︎ I can't even stand up anymoreee! ♥︎", + "creator_name": "garfieldfromgarfield" + }, + { + "id": 386291, + "created_at": "2023-10-24T04:25:58.415-04:00", + "updated_at": "2023-10-24T04:25:58.415-04:00", + "creator_id": 454665, + "x": 1609, + "y": 755, + "width": 189, + "height": 508, + "version": 2, + "is_active": true, + "post_id": 4367223, + "body": "What an exquisite cock...please bestow your glorious semen upon this lowly, vulgar hero! ♥︎", + "creator_name": "garfieldfromgarfield" + }, + { + "id": 386290, + "created_at": "2023-10-24T04:24:05.857-04:00", + "updated_at": "2023-10-24T04:24:05.857-04:00", + "creator_id": 479627, + "x": 40, + "y": 218, + "width": 84, + "height": 37, + "version": 2, + "is_active": true, + "post_id": 4370526, + "body": "H-huge...", + "creator_name": "Jinn" + }, + { + "id": 386289, + "created_at": "2023-10-24T04:23:08.494-04:00", + "updated_at": "2023-10-24T04:23:08.494-04:00", + "creator_id": 454665, + "x": 1856, + "y": 1050, + "width": 107, + "height": 160, + "version": 2, + "is_active": true, + "post_id": 4367223, + "body": "OGHH! ♥︎", + "creator_name": "garfieldfromgarfield" + }, + { + "id": 386288, + "created_at": "2023-10-24T04:23:00.714-04:00", + "updated_at": "2023-10-24T04:23:00.714-04:00", + "creator_id": 454665, + "x": 1833, + "y": 620, + "width": 52, + "height": 120, + "version": 2, + "is_active": true, + "post_id": 4367223, + "body": "Nhohh!", + "creator_name": "garfieldfromgarfield" + }, + { + "id": 386287, + "created_at": "2023-10-24T04:20:55.934-04:00", + "updated_at": "2023-10-24T04:20:55.934-04:00", + "creator_id": 479627, + "x": 487, + "y": 563, + "width": 68, + "height": 177, + "version": 2, + "is_active": true, + "post_id": 4370526, + "body": "N-NOOOOO!!", + "creator_name": "Jinn" + }, + { + "id": 386286, + "created_at": "2023-10-24T04:16:33.715-04:00", + "updated_at": "2023-10-24T04:16:33.715-04:00", + "creator_id": 479627, + "x": 504, + "y": 149, + "width": 84, + "height": 195, + "version": 2, + "is_active": true, + "post_id": 4370526, + "body": "EEEEEEEEK!", + "creator_name": "Jinn" + }, + { + "id": 386285, + "created_at": "2023-10-24T04:16:05.237-04:00", + "updated_at": "2023-10-24T04:16:05.237-04:00", + "creator_id": 479627, + "x": 776, + "y": 26, + "width": 255, + "height": 64, + "version": 2, + "is_active": true, + "post_id": 4370526, + "body": "How dare a tiny bee act insolently towards the Wasp King!", + "creator_name": "Jinn" + }, + { + "id": 386284, + "created_at": "2023-10-24T04:03:21.229-04:00", + "updated_at": "2023-10-24T04:03:21.229-04:00", + "creator_id": 479627, + "x": 326, + "y": 45, + "width": 171, + "height": 37, + "version": 2, + "is_active": true, + "post_id": 4370526, + "body": "I have to teach you some manners.", + "creator_name": "Jinn" + }, + { + "id": 386283, + "created_at": "2023-10-24T03:38:00.800-04:00", + "updated_at": "2023-10-24T03:38:00.800-04:00", + "creator_id": 479627, + "x": 989, + "y": 735, + "width": 91, + "height": 45, + "version": 2, + "is_active": true, + "post_id": 4370526, + "body": "1 week later", + "creator_name": "Jinn" + }, + { + "id": 386282, + "created_at": "2023-10-23T23:05:55.154-04:00", + "updated_at": "2023-10-23T23:05:55.154-04:00", + "creator_id": 934802, + "x": 141, + "y": 3572, + "width": 129, + "height": 320, + "version": 2, + "is_active": true, + "post_id": 4370292, + "body": "YESSIR!", + "creator_name": "Benjiboyo" + }, + { + "id": 386281, + "created_at": "2023-10-23T23:05:47.529-04:00", + "updated_at": "2023-10-23T23:05:47.529-04:00", + "creator_id": 934802, + "x": 1441, + "y": 2850, + "width": 222, + "height": 561, + "version": 2, + "is_active": true, + "post_id": 4370292, + "body": "I'll take care of the printings!", + "creator_name": "Benjiboyo" + }, + { + "id": 386280, + "created_at": "2023-10-23T23:05:28.573-04:00", + "updated_at": "2023-10-23T23:05:28.573-04:00", + "creator_id": 934802, + "x": 2477, + "y": 1829, + "width": 227, + "height": 724, + "version": 2, + "is_active": true, + "post_id": 4370292, + "body": "Allright! First, you gotta finish the corrections!", + "creator_name": "Benjiboyo" + }, + { + "id": 386279, + "created_at": "2023-10-23T23:04:47.329-04:00", + "updated_at": "2023-10-23T23:07:41.510-04:00", + "creator_id": 934802, + "x": 93, + "y": 933, + "width": 595, + "height": 165, + "version": 4, + "is_active": true, + "post_id": 4370292, + "body": "Let's get on call now!", + "creator_name": "Benjiboyo" + }, + { + "id": 386278, + "created_at": "2023-10-23T23:04:37.038-04:00", + "updated_at": "2023-10-23T23:07:22.208-04:00", + "creator_id": 934802, + "x": 56, + "y": 177, + "width": 824, + "height": 172, + "version": 3, + "is_active": true, + "post_id": 4370292, + "body": "Yeah, I can still go! I'm ready to fight!", + "creator_name": "Benjiboyo" + }, + { + "id": 386277, + "created_at": "2023-10-23T23:00:55.676-04:00", + "updated_at": "2023-10-23T23:00:55.676-04:00", + "creator_id": 934802, + "x": 784, + "y": 62, + "width": 493, + "height": 1349, + "version": 2, + "is_active": true, + "post_id": 4370292, + "body": "I just need to make the corrections and rewrite the back cover\n\nAnd just 1 last go over of the whole manuscript to be sure", + "creator_name": "Benjiboyo" + }, + { + "id": 386276, + "created_at": "2023-10-23T22:55:56.905-04:00", + "updated_at": "2023-10-23T22:55:56.905-04:00", + "creator_id": 934802, + "x": 2509, + "y": 1116, + "width": 252, + "height": 302, + "version": 2, + "is_active": true, + "post_id": 4370292, + "body": "I can't give up just yet!!", + "creator_name": "Benjiboyo" + }, + { + "id": 386275, + "created_at": "2023-10-23T22:55:03.619-04:00", + "updated_at": "2023-10-23T22:55:03.619-04:00", + "creator_id": 934802, + "x": 2580, + "y": 205, + "width": 90, + "height": 236, + "version": 2, + "is_active": true, + "post_id": 4370292, + "body": "He's right", + "creator_name": "Benjiboyo" + } +] \ No newline at end of file diff --git a/tests/pool.json b/tests/pool.json new file mode 100644 index 0000000..6e7e80b --- /dev/null +++ b/tests/pool.json @@ -0,0 +1,38 @@ +{ + "id": 1, + "name": "Furry_Boys_by_Trump/Team_Shuffle", + "created_at": "2008-10-30T03:42:29.962-04:00", + "updated_at": "2023-10-24T06:58:59.488-04:00", + "creator_id": 7, + "description": "Furry Boys Comic", + "is_active": false, + "category": "series", + "post_ids": [ + 3294, + 3295, + 3296, + 3297, + 3298, + 3299, + 3300, + 3303, + 3306, + 3304, + 3305, + 3307, + 3309, + 3308, + 3313, + 3312, + 3316, + 3314, + 3315, + 3321, + 3317, + 3319, + 3318, + 3320 + ], + "creator_name": "user_7", + "post_count": 24 +} \ No newline at end of file diff --git a/tests/pools.json b/tests/pools.json new file mode 100644 index 0000000..31b9770 --- /dev/null +++ b/tests/pools.json @@ -0,0 +1,4002 @@ +[ + { + "id": 29704, + "name": "Druids_-_Far_From_The_Tree", + "created_at": "2022-07-27T22:06:56.834-04:00", + "updated_at": "2023-10-24T09:40:10.340-04:00", + "creator_id": 461163, + "description": "", + "is_active": true, + "category": "series", + "post_ids": [ + 3471796, + 3649951, + 3731739, + 3737832, + 3745878, + 3755895, + 3759982, + 3861988, + 3873505, + 3908379, + 3908381, + 3930503, + 3968209, + 4024938, + 4052580, + 4132983, + 4132984, + 4132986, + 4132989, + 4132991, + 4188901, + 4188902, + 4188903, + 4188904, + 4204546, + 4218081, + 4233365, + 4245241, + 4258487, + 4271468, + 4288168, + 4298026, + 4309687, + 4344807, + 4344809, + 4371100, + 4371101 + ], + "creator_name": "Alpha7505", + "post_count": 37 + }, + { + "id": 37105, + "name": "Buntober_(lightmizano)", + "created_at": "2023-10-21T14:09:07.167-04:00", + "updated_at": "2023-10-24T09:01:14.230-04:00", + "creator_id": 1012446, + "description": "", + "is_active": true, + "category": "collection", + "post_ids": [ + 4352451, + 4360691, + 4367401, + 4367404, + 4367406, + 4367412, + 4367420, + 4367421, + 4367947, + 4367952, + 4367961, + 4367971, + 4367982, + 4367993, + 4368013, + 4368023, + 4368032, + 4368042, + 4368964, + 4368969, + 4368977, + 4368980, + 4368982, + 4369297, + 4371068 + ], + "creator_name": "prin34", + "post_count": 25 + }, + { + "id": 37145, + "name": "A_Bat_Followed_Me_Home", + "created_at": "2023-10-23T21:14:28.366-04:00", + "updated_at": "2023-10-24T08:40:48.650-04:00", + "creator_id": 732974, + "description": "by [[Shane Frost]]\r\n\r\n[quote]\r\nDaniel Toke was your average isolated workaholic. That is of course until everything started getting batty after he found a box on the way home. At first it’s innocent enough a gesture when he finds what looks like a sick bat in the box, but Daniel has started something that he can’t walk away from. It’s different, it’s abnormal, and it’s hungry. Can Daniel accept the responsibility or did he make a grave mistake?\r\n[/quote]", + "is_active": false, + "category": "series", + "post_ids": [ + 4370151, + 4370243, + 4370268, + 4370326, + 4370890, + 4370907, + 4371059 + ], + "creator_name": "SNPtheCat", + "post_count": 7 + }, + { + "id": 36633, + "name": "Kinktober_2023_by_Sincrescent", + "created_at": "2023-10-01T09:00:29.647-04:00", + "updated_at": "2023-10-24T08:01:27.636-04:00", + "creator_id": 224736, + "description": "", + "is_active": true, + "category": "collection", + "post_ids": [ + 4325235, + 4327288, + 4329502, + 4331700, + 4333746, + 4335689, + 4337734, + 4339815, + 4341986, + 4343859, + 4345766, + 4349643, + 4351742, + 4353599, + 4355354, + 4357201, + 4358986, + 4362866, + 4363059, + 4365056, + 4367028, + 4369024, + 4371014 + ], + "creator_name": "Acron", + "post_count": 23 + }, + { + "id": 37147, + "name": "They_are_Bigger_In_Texas", + "created_at": "2023-10-24T07:33:58.356-04:00", + "updated_at": "2023-10-24T07:33:58.356-04:00", + "creator_id": 14014, + "description": "A series of [[Sandy Cheeks]] pictures by [[Slimefur]].", + "is_active": true, + "category": "series", + "post_ids": [ + 4370842, + 4370846, + 4370853, + 4370857, + 4370858 + ], + "creator_name": "Clawstripe", + "post_count": 5 + }, + { + "id": 1, + "name": "Furry_Boys_by_Trump/Team_Shuffle", + "created_at": "2008-10-30T03:42:29.962-04:00", + "updated_at": "2023-10-24T06:58:59.488-04:00", + "creator_id": 7, + "description": "Furry Boys Comic", + "is_active": false, + "category": "series", + "post_ids": [ + 3294, + 3295, + 3296, + 3297, + 3298, + 3299, + 3300, + 3303, + 3306, + 3304, + 3305, + 3307, + 3309, + 3308, + 3313, + 3312, + 3316, + 3314, + 3315, + 3321, + 3317, + 3319, + 3318, + 3320 + ], + "creator_name": "user_7", + "post_count": 24 + }, + { + "id": 36036, + "name": "Lab_Demonstration", + "created_at": "2023-08-28T19:15:00.943-04:00", + "updated_at": "2023-10-24T05:29:06.159-04:00", + "creator_id": 857924, + "description": "Inspector Pebble is running a relatively normal checkup on lead scientist Wanda's work. That is, right up until a catastrophic mistake makes what would be a simple demonstration of her new invention's capability into a far more... costly affair.", + "is_active": true, + "category": "series", + "post_ids": [ + 4259001, + 4260075, + 4273259, + 4286624, + 4300136, + 4313865, + 4328468, + 4342966, + 4356322, + 4370889 + ], + "creator_name": "Furufoo", + "post_count": 10 + }, + { + "id": 37139, + "name": "Dearest_2_crunch_story_-_(Side_Wolfonia)", + "created_at": "2023-10-23T10:54:10.746-04:00", + "updated_at": "2023-10-24T05:06:58.277-04:00", + "creator_id": 934802, + "description": "そんな修羅場から生まれた合同誌、「Dearest 2」の通販ページがこちらでございます。\r\n表紙本文にもれなくキズ・汚れがございますが、みんな共に戦った証の傷でございます。何卒よろしくお願いいたします。\r\nhttps://alice-books.com/item/show/10508-2\r\n\r\n\"Side YamatoKuroko\":/pools/36609", + "is_active": true, + "category": "series", + "post_ids": [ + 4369228, + 4369229, + 4369230, + 4369293, + 4369294, + 4369295, + 4369296, + 4370289, + 4370291, + 4370292, + 4370293, + 4370864, + 4370865, + 4370866, + 4370867 + ], + "creator_name": "Benjiboyo", + "post_count": 15 + }, + { + "id": 26461, + "name": "Passionate_Affection_by_booboo", + "created_at": "2021-11-28T19:35:01.806-05:00", + "updated_at": "2023-10-24T04:08:14.364-04:00", + "creator_id": 488034, + "description": "", + "is_active": true, + "category": "series", + "post_ids": [ + 3048401, + 3048419, + 3048447, + 3048465, + 3055643, + 3055652, + 3055655, + 4272174, + 4272190, + 4272198, + 4273088, + 4273406, + 4273592, + 4273593, + 4273641, + 4273759, + 4273779, + 4273884, + 4273885, + 4273919, + 4273973, + 4273979, + 4273989, + 4274002, + 4274049, + 4276046, + 4276055, + 4276068, + 4276093, + 4276108, + 4278087, + 4278202, + 4278219, + 4281774, + 4286744, + 4286764, + 4286772, + 4286782, + 4286795, + 4286840, + 4286916, + 4294550, + 4294565, + 4294570, + 4294582, + 4294598, + 4294603, + 4294611, + 4294622, + 4308103, + 4328925, + 4328926, + 4330311, + 4353384, + 4353385, + 4353397, + 4353398, + 4353399, + 4353416, + 4353425, + 4354472, + 4354668, + 4355213, + 4355214, + 4358295, + 4360653, + 4360654, + 4360747, + 4360748, + 4360762, + 4370760, + 4370761, + 4370763, + 4370781, + 4370782, + 4370783, + 4370784, + 4370792, + 4370793, + 4370804 + ], + "creator_name": "OriginalName333", + "post_count": 80 + }, + { + "id": 37024, + "name": "Orbit:Encounter_(by_mgferret)", + "created_at": "2023-10-17T03:28:12.290-04:00", + "updated_at": "2023-10-24T02:28:28.522-04:00", + "creator_id": 453504, + "description": "Orbit is a horror-thriller adventure adult furry comic about the loner, Oscar. Solitary in an environment filled with a whole range of beings, he is sent out for his first solo mission to salvage a storage device from the ship New Discovery, that reportedly was evacuated 5 years ago. Everyone thought it would a be simple job, for 'the little fella', but accidentally discovering your origins can be a truly terrifying task.\r\n\r\nRead ahead at https://www.patreon.com/orbitencounter", + "is_active": true, + "category": "series", + "post_ids": [ + 4357039, + 4357040, + 4357041, + 4357042, + 4357043, + 4357044, + 4357045, + 4357046, + 4357047, + 4357049, + 4360055, + 4360056, + 4360057, + 4360059, + 4360060, + 4360061, + 4360062, + 4360064, + 4360065, + 4360066, + 4360067, + 4360068, + 4360069, + 4360070, + 4360071, + 4360072, + 4360073, + 4360074, + 4360075, + 4360076, + 4360077, + 4360078, + 4360079, + 4360080, + 4360081, + 4360082, + 4360084, + 4360086, + 4360088, + 4360090, + 4362782, + 4362783, + 4362784, + 4362785, + 4362786, + 4362787, + 4362788, + 4362789, + 4362790, + 4362791, + 4362792, + 4362793, + 4362795, + 4362796, + 4362797, + 4362798, + 4362799, + 4362800, + 4362801, + 4362802, + 4362804, + 4362805, + 4362806, + 4362807, + 4362808, + 4362811, + 4362813, + 4362814, + 4362815, + 4362816, + 4370333, + 4370334, + 4370336, + 4370338, + 4370340, + 4370342, + 4370343, + 4370344, + 4370345, + 4370346, + 4370349, + 4370350, + 4370351, + 4370352, + 4370353, + 4370355, + 4370356, + 4370357, + 4370358, + 4370359, + 4370360, + 4370361, + 4370362, + 4370363, + 4370364, + 4370365, + 4370366, + 4370367, + 4370368, + 4370369, + 4370735, + 4370736, + 4370737, + 4370738, + 4370739, + 4370740 + ], + "creator_name": "WeRideAtDawn", + "post_count": 106 + }, + { + "id": 35816, + "name": "Sleep_Through_Your_Alarms", + "created_at": "2023-08-17T03:47:50.954-04:00", + "updated_at": "2023-10-24T02:23:17.560-04:00", + "creator_id": 14014, + "description": "A [[pokemon|Pokémon]] [[comic]] by [[conniethewolf|Connie the Wolf]].", + "is_active": true, + "category": "series", + "post_ids": [ + 4188949, + 4203892, + 4218552, + 4229579, + 4277949, + 4277963, + 4277967, + 4368382, + 4368408 + ], + "creator_name": "Clawstripe", + "post_count": 9 + }, + { + "id": 37133, + "name": "Evil_Breeding_#!", + "created_at": "2023-10-22T23:56:18.165-04:00", + "updated_at": "2023-10-24T01:54:44.677-04:00", + "creator_id": 639353, + "description": "It is the first comic I have made of this character, an aracnosaur, if it receives support I will be creating more content about it...\r\n\r\n\r\n\r\na thank you to Kiaradoggo for allowing me to use his characters", + "is_active": true, + "category": "series", + "post_ids": [ + 4368368, + 4368376, + 4368384, + 4368391, + 4368396, + 4368606, + 4370641, + 4370697 + ], + "creator_name": "slasheramaya", + "post_count": 8 + }, + { + "id": 37040, + "name": "Say_Uncle", + "created_at": "2023-10-17T18:03:42.326-04:00", + "updated_at": "2023-10-24T01:47:45.938-04:00", + "creator_id": 1513618, + "description": "Comic by P_RIndustries about how Will introduces Chuck and Fred.", + "is_active": true, + "category": "series", + "post_ids": [ + 4358062, + 4358073, + 4358077, + 4358080, + 4358082, + 4358089, + 4358093, + 4367346, + 4367347, + 4370656, + 4370663, + 4370670 + ], + "creator_name": "Viewer202020", + "post_count": 12 + }, + { + "id": 36579, + "name": "Family_tradition_by_spiralart_(artist)", + "created_at": "2023-09-28T15:02:25.331-04:00", + "updated_at": "2023-10-24T00:19:34.219-04:00", + "creator_id": 628038, + "description": "", + "is_active": true, + "category": "series", + "post_ids": [ + 4272448, + 4312963, + 4370493 + ], + "creator_name": "Coatl", + "post_count": 3 + }, + { + "id": 33925, + "name": "Leaving_The_Cradle_-_Volume_1", + "created_at": "2023-04-26T13:57:06.105-04:00", + "updated_at": "2023-10-24T00:08:31.251-04:00", + "creator_id": 453504, + "description": "Created by darth-biomech.\r\n\r\nUploaded with partial permission from the artist. Pages from the prologue and first chapter are not to be posted.\r\n\r\nFull comic can be found here: https://leavingthecradle.com/comic/archive\r\n", + "is_active": true, + "category": "series", + "post_ids": [ + 4016664, + 4015886, + 4015887, + 4015889, + 4015890, + 4015891, + 4015892, + 4015893, + 4015895, + 4015896, + 4015897, + 4016112, + 4016113, + 4016114, + 4016115, + 4016116, + 4016117, + 4016118, + 4016119, + 4016120, + 4016121, + 4016122, + 4016124, + 4016125, + 4016127, + 4016128, + 4016129, + 4016695, + 4016696, + 4016697, + 4017100, + 4017101, + 4017102, + 4017103, + 4017104, + 4017105, + 4017106, + 4017107, + 4017108, + 4017109, + 4017110, + 4017136, + 4017137, + 4017139, + 4017140, + 4017141, + 4017142, + 4017143, + 4017144, + 4017145, + 4017146, + 4017170, + 4017171, + 4017172, + 4017173, + 4017174, + 4017175, + 4017177, + 4017178, + 4017179, + 4017197, + 4017198, + 4017199, + 4017200, + 4017201, + 4017202, + 4017203, + 4017204, + 4018321, + 4018322, + 4018323, + 4018324, + 4018325, + 4018326, + 4018327, + 4018328, + 4018329, + 4018330, + 4018331, + 4018332, + 4018333, + 4018334, + 4018335, + 4018336, + 4018338, + 4018339, + 4018341, + 4018342, + 4018343, + 4018344, + 4018345, + 4018347, + 4018348, + 4018349, + 4018350, + 4018351, + 4018352, + 4018353, + 4018563, + 4018564, + 4018565, + 4018566, + 4018567, + 4018568, + 4018571, + 4018572, + 4018573, + 4018574, + 4018575, + 4018576, + 4018577, + 4018578, + 4018579, + 4018580, + 4018581, + 4018582, + 4018583, + 4018584, + 4018585, + 4018586, + 4018587, + 4018589, + 4018590, + 4018591, + 4018592, + 4026601, + 4041086, + 4056545, + 4067076, + 4080549, + 4093840, + 4106952, + 4119963, + 4134882, + 4148892, + 4162851, + 4177175, + 4191077, + 4205985, + 4219843, + 4233708, + 4246998, + 4259966, + 4273148, + 4286415, + 4300195, + 4313357, + 4328327, + 4342727, + 4356213, + 4370473 + ], + "creator_name": "WeRideAtDawn", + "post_count": 151 + }, + { + "id": 14419, + "name": "Blazing_A_Trail_-_fuf", + "created_at": "2018-05-10T15:08:56.253-04:00", + "updated_at": "2023-10-23T23:52:45.654-04:00", + "creator_id": 332342, + "description": "Comic about a [[pokemon_trainer]] named [[Kyra_(fuf)|Kyra]] and her pokemon's antics as she tries to gather gym badges.\r\n\r\nMade by [[Fuf]].", + "is_active": true, + "category": "series", + "post_ids": [ + 1540252, + 1541023, + 1541026, + 1541036, + 1541045, + 1541057, + 1541060, + 1541067, + 1541075, + 1541079, + 1541082, + 1541087, + 1550662, + 1564135, + 1575248, + 1587411, + 1599005, + 1606006, + 1612331, + 1624568, + 1635804, + 1660866, + 1681550, + 1686431, + 1699930, + 1712746, + 1723388, + 1738633, + 1750838, + 1765184, + 1777945, + 1789560, + 1801595, + 1833470, + 1835137, + 1845449, + 1853355, + 1858613, + 1866644, + 1877790, + 1883508, + 1898680, + 1907218, + 1914489, + 1932345, + 1946637, + 1953157, + 1959393, + 1970338, + 1983205, + 2004482, + 2033032, + 2050720, + 2062292, + 2084323, + 2100197, + 2111770, + 2118045, + 2145283, + 2163309, + 2178602, + 2178616, + 2191290, + 2204093, + 2221852, + 2231269, + 2270360, + 2297840, + 2318744, + 2351744, + 2351745, + 2365698, + 2374844, + 2393532, + 2406225, + 2431865, + 2431866, + 2466407, + 2466095, + 2474321, + 2502951, + 2526022, + 2582621, + 2605647, + 2641031, + 2644414, + 2690111, + 2722834, + 2724205, + 2733311, + 2746777, + 2789202, + 2790737, + 2810805, + 2815039, + 2819786, + 2822325, + 2838372, + 2845047, + 2855371, + 2872834, + 2879962, + 2885744, + 2890293, + 2896141, + 2906650, + 2916802, + 2931665, + 2946141, + 2961291, + 2974850, + 2989672, + 3002308, + 3012112, + 3043365, + 3048690, + 3061857, + 3073652, + 3085387, + 3095865, + 3109851, + 3119324, + 3133275, + 3161909, + 3161914, + 3173240, + 3186952, + 3199974, + 3210789, + 3224041, + 3240278, + 3254438, + 3316062, + 3316815, + 3316820, + 3316821, + 3327648, + 3321586, + 3322292, + 3322301, + 3322322, + 3322336, + 3322340, + 3322350, + 3322361, + 3322364, + 3322374, + 3322378, + 3322380, + 3322388, + 3322392, + 3322393, + 3322405, + 3322411, + 3322428, + 3322432, + 3322443, + 3322453, + 3325912, + 3328428, + 3328430, + 3328549, + 3328432, + 3328433, + 3328434, + 3328435, + 3328436, + 3328437, + 3328438, + 3328439, + 3328440, + 3328441, + 3328443, + 3328445, + 3328447, + 3328448, + 3328449, + 3328450, + 3328451, + 3328452, + 3333122, + 3333126, + 3333128, + 3333131, + 3333132, + 3336653, + 3336660, + 3336671, + 3336676, + 3338114, + 3339721, + 3343754, + 3343759, + 3369481, + 3369483, + 3387315, + 3401968, + 3413035, + 3424342, + 3447400, + 3473415, + 3535766, + 3535776, + 3535783, + 3559048, + 3582691, + 3697730, + 3697736, + 3697739, + 3697743, + 3717478, + 3723794, + 3723798, + 3723804, + 3723807, + 3723812, + 3723815, + 3723821, + 3723825, + 3723829, + 3723833, + 3723886, + 3723904, + 3723910, + 3734501, + 3734503, + 3734506, + 3734509, + 3734512, + 3734515, + 3774684, + 3774694, + 3774696, + 3774700, + 3774702, + 3774703, + 3774706, + 3774711, + 3774710, + 3774714, + 3778187, + 3778188, + 3774719, + 3774723, + 3774731, + 3776397, + 3776398, + 3776405, + 3776411, + 3776419, + 3776425, + 3776443, + 3776454, + 3776472, + 3776481, + 3776487, + 3776491, + 3789333, + 3789350, + 3789356, + 3789365, + 3789370, + 3789377, + 3789386, + 3790341, + 3797929, + 3800909, + 3803092, + 3805640, + 3808555, + 3812313, + 3815524, + 3818619, + 3822452, + 3844410, + 3844417, + 3844426, + 3844435, + 3844443, + 3858320, + 3883835, + 3949668, + 3959331, + 3998306, + 3998313, + 4060257, + 4060258, + 4060267, + 4090934, + 4090939, + 4095896, + 4106871, + 4122235, + 4161250, + 4161253, + 4161258, + 4161260, + 4178745, + 4207596, + 4207613, + 4227092, + 4227095, + 4245501, + 4246479, + 4258377, + 4287132, + 4296199, + 4298782, + 4316721, + 4316724, + 4316730, + 4346736, + 4370434 + ], + "creator_name": "user_332342", + "post_count": 313 + }, + { + "id": 37055, + "name": "Tweetfur_strip_game_by_Qrichy", + "created_at": "2023-10-19T01:15:34.856-04:00", + "updated_at": "2023-10-23T23:45:27.547-04:00", + "creator_id": 1314348, + "description": "", + "is_active": true, + "category": "series", + "post_ids": [ + 4358772, + 4360552, + 4370409, + 4370418 + ], + "creator_name": "witnessbento", + "post_count": 4 + }, + { + "id": 20099, + "name": "Star_Ship_-_by_toomuchdynamite", + "created_at": "2020-03-30T23:59:25.359-04:00", + "updated_at": "2023-10-23T23:31:29.151-04:00", + "creator_id": 248302, + "description": "", + "is_active": true, + "category": "collection", + "post_ids": [ + 2104678, + 2108471, + 2116478, + 2436136, + 2469013, + 2511615, + 2152981, + 2160283, + 2168325, + 2175269, + 2181572, + 2463535, + 2197758, + 2466141, + 2466146, + 2466150, + 2233184, + 2243261, + 2465056, + 2465046, + 2270214, + 2418519, + 2303881, + 2303885, + 2419525, + 2419532, + 2418521, + 2419534, + 2419543, + 2419552, + 2419554, + 2419558, + 2419572, + 2394648, + 2419574, + 2419575, + 2418507, + 2426841, + 2436549, + 2444823, + 2453392, + 2462110, + 2471924, + 2481069, + 2490243, + 2500138, + 2509902, + 2519471, + 2537686, + 2537819, + 2547625, + 2577320, + 2586610, + 2596956, + 2616400, + 2625891, + 2646368, + 2654945, + 2671142, + 2674628, + 2684213, + 2704998, + 2714012, + 2784497, + 2838788, + 2823877, + 2784750, + 2795602, + 2823912, + 2859954, + 3006390, + 3339344, + 3339348, + 3339352, + 3339357, + 3417210, + 3469095, + 3706493, + 3782423, + 3811811, + 4335950, + 4335952, + 4335955, + 4370397 + ], + "creator_name": "fox263", + "post_count": 84 + }, + { + "id": 36714, + "name": "drow462’s_Sketchtober_/_Inktober_2023", + "created_at": "2023-10-03T10:21:29.033-04:00", + "updated_at": "2023-10-23T23:22:55.714-04:00", + "creator_id": 524433, + "description": "Previous year: https://e621.net/pools/30797", + "is_active": true, + "category": "collection", + "post_ids": [ + 4326796, + 4328876, + 4331059, + 4333064, + 4334930, + 4337104, + 4338999, + 4341443, + 4343366, + 4343568, + 4344365, + 4346544, + 4348731, + 4350381, + 4352528, + 4353985, + 4356064, + 4358338, + 4360458, + 4362576, + 4364194, + 4365909, + 4368171, + 4370385 + ], + "creator_name": "drow462", + "post_count": 24 + }, + { + "id": 33432, + "name": "Ferret_Family_by_Tegerio", + "created_at": "2023-03-25T21:35:30.228-04:00", + "updated_at": "2023-10-23T23:10:02.326-04:00", + "creator_id": 192543, + "description": "", + "is_active": true, + "category": "series", + "post_ids": [ + 3584929, + 3584941, + 3584868, + 3740815, + 3774010, + 3953666, + 3953678, + 3953683, + 4047273, + 4098061, + 4135666, + 4203631, + 4262869, + 4370375 + ], + "creator_name": "Googlipod", + "post_count": 14 + }, + { + "id": 36580, + "name": "Cherubee", + "created_at": "2023-09-28T15:41:18.454-04:00", + "updated_at": "2023-10-23T23:00:16.335-04:00", + "creator_id": 1034913, + "description": "a slice of life comic focusing on Collin/Beelzebub ", + "is_active": true, + "category": "series", + "post_ids": [ + 4285869, + 4319445, + 4370332 + ], + "creator_name": "gameguy82", + "post_count": 3 + }, + { + "id": 36182, + "name": "Commander's_Open_Penis_Chart", + "created_at": "2023-09-06T23:29:22.945-04:00", + "updated_at": "2023-10-23T22:46:24.551-04:00", + "creator_id": 375012, + "description": "this is another penis chart with slots open for purchase", + "is_active": true, + "category": "series", + "post_ids": [ + 4277771, + 4277826, + 4277841, + 4277844, + 4277914, + 4278276, + 4278598, + 4279914, + 4290853, + 4280436, + 4280439, + 4280441, + 4290856, + 4290845, + 4291002, + 4292792, + 4293264, + 4293146, + 4293428, + 4294489, + 4294492, + 4294506, + 4298544, + 4298546, + 4302262, + 4320663, + 4320665, + 4320671, + 4323738, + 4323743, + 4323752, + 4329525, + 4329528, + 4329536, + 4329540, + 4330352, + 4330569, + 4340805, + 4340889, + 4342413, + 4345142, + 4346192, + 4346864, + 4348604, + 4348722, + 4365728, + 4366104, + 4370190 + ], + "creator_name": "CommanderThings", + "post_count": 48 + }, + { + "id": 37146, + "name": "Vextrum_\u0026_Yani_Head", + "created_at": "2023-10-23T22:39:06.542-04:00", + "updated_at": "2023-10-23T22:40:32.573-04:00", + "creator_id": 1164645, + "description": "", + "is_active": true, + "category": "collection", + "post_ids": [ + 4365629, + 4369700 + ], + "creator_name": "ItsVextrum", + "post_count": 2 + }, + { + "id": 36926, + "name": "Darbedarmoc's_Animated_Kinktober_-_2023", + "created_at": "2023-10-12T02:07:35.019-04:00", + "updated_at": "2023-10-23T22:40:14.325-04:00", + "creator_id": 214376, + "description": "", + "is_active": true, + "category": "series", + "post_ids": [ + 4323038, + 4325688, + 4327880, + 4330178, + 4332043, + 4334215, + 4336124, + 4338275, + 4340475, + 4342537, + 4344168, + 4346305, + 4348150, + 4350094, + 4352209, + 4354180, + 4355652, + 4357686, + 4359512, + 4361344, + 4363600, + 4365840, + 4367747, + 4369685 + ], + "creator_name": "TwiNaga", + "post_count": 24 + }, + { + "id": 35978, + "name": "A_Night_With_Loona_2", + "created_at": "2023-08-25T21:11:06.956-04:00", + "updated_at": "2023-10-23T21:36:41.839-04:00", + "creator_id": 191225, + "description": "-Millie uses a magical disguise to impersonate Moxxie to get the truth straight from Loona's mouth. In this second part of the comic \"A night with Loona\" Moxxie, Loona, and Millie will learn what they really want for their future.-\r\n\r\n* \"Chapter 1\":/pools/23886\r\n* [b]Chapter 2[/b]", + "is_active": true, + "category": "series", + "post_ids": [ + 4254529, + 4370189 + ], + "creator_name": "cinnamon365", + "post_count": 2 + }, + { + "id": 36632, + "name": "Spitey's_Fucktober_2023", + "created_at": "2023-10-01T08:32:18.128-04:00", + "updated_at": "2023-10-23T21:08:02.819-04:00", + "creator_id": 224736, + "description": "", + "is_active": true, + "category": "series", + "post_ids": [ + 4325203, + 4327247, + 4329309, + 4331655, + 4333600, + 4335604, + 4337641, + 4339951, + 4342072, + 4343912, + 4345970, + 4347663, + 4349674, + 4351797, + 4353640, + 4355660, + 4357365, + 4359262, + 4361119, + 4363089, + 4365408, + 4367618, + 4370146 + ], + "creator_name": "Acron", + "post_count": 23 + }, + { + "id": 17153, + "name": "Abubu_-_Little_Red_Riding_Hood", + "created_at": "2019-04-17T16:50:46.327-04:00", + "updated_at": "2023-10-23T21:00:18.695-04:00", + "creator_id": 10799, + "description": "A small short by Abubu.", + "is_active": false, + "category": "series", + "post_ids": [ + 1846725, + 1846729, + 1846737, + 1847119, + 1847120, + 1846783 + ], + "creator_name": "laranja", + "post_count": 6 + }, + { + "id": 11238, + "name": "Abubu_-_成人の儀式でメスケモレイプするショタ部族", + "created_at": "2017-03-29T11:04:44.658-04:00", + "updated_at": "2023-10-23T20:51:31.262-04:00", + "creator_id": 38571, + "description": "by {{abubu}}", + "is_active": false, + "category": "series", + "post_ids": [ + 1175773, + 1175772, + 1175774, + 1175775, + 1175879, + 940047 + ], + "creator_name": "Mairo", + "post_count": 6 + }, + { + "id": 36838, + "name": "Unwanted_Animal", + "created_at": "2023-10-07T22:46:58.346-04:00", + "updated_at": "2023-10-23T20:20:59.282-04:00", + "creator_id": 430869, + "description": "Experimental comic by {{carrioncat}}", + "is_active": true, + "category": "series", + "post_ids": [ + 4321806, + 4338914, + 4352794, + 4370063 + ], + "creator_name": "tredfg543", + "post_count": 4 + }, + { + "id": 36760, + "name": "Sexy_Catober_2023", + "created_at": "2023-10-04T22:17:05.362-04:00", + "updated_at": "2023-10-23T19:55:22.317-04:00", + "creator_id": 336260, + "description": "", + "is_active": true, + "category": "collection", + "post_ids": [ + 4326716, + 4328788, + 4332911, + 4332926, + 4335117, + 4336940, + 4339048, + 4341226, + 4343357, + 4344928, + 4346994, + 4349108, + 4351535, + 4353157, + 4354875, + 4319917, + 4356608, + 4358891, + 4362249, + 4364686, + 4366836, + 4368496, + 4370048 + ], + "creator_name": "Coopercaller", + "post_count": 23 + }, + { + "id": 37144, + "name": "Play_of_the_Game", + "created_at": "2023-10-23T19:37:06.784-04:00", + "updated_at": "2023-10-23T19:37:06.784-04:00", + "creator_id": 369020, + "description": "", + "is_active": true, + "category": "series", + "post_ids": [ + 4308162, + 4308163, + 4369995, + 4369998, + 4370001, + 4370003 + ], + "creator_name": "Poofinator", + "post_count": 6 + }, + { + "id": 35845, + "name": "Internshipped_(schpicy)", + "created_at": "2023-08-19T11:02:51.839-04:00", + "updated_at": "2023-10-23T19:33:33.662-04:00", + "creator_id": 396514, + "description": "", + "is_active": true, + "category": "series", + "post_ids": [ + 4242230, + 4242231, + 4242234, + 4242233, + 4242235, + 4242236, + 4293973, + 4302062, + 4316153, + 4360428, + 4360430, + 4360431, + 4369996 + ], + "creator_name": "GetAGrip", + "post_count": 13 + }, + { + "id": 32736, + "name": "Bound_by_Duty_by_RobCivecat", + "created_at": "2023-02-09T11:09:28.505-05:00", + "updated_at": "2023-10-23T19:05:47.540-04:00", + "creator_id": 461163, + "description": "After Nick endangered Judy by not muzzling a criminal they were arresting, he decides that it's time that he rids himself of his phobia once and for all. He must, if he wants to keep being her partner. Hopefully Judy can help him with this hard task.", + "is_active": true, + "category": "series", + "post_ids": [ + 3862852, + 3862853, + 3884592, + 3924207, + 3960451, + 3998638, + 4050130, + 4098479, + 4167832, + 4242717, + 4311305, + 4369960 + ], + "creator_name": "Alpha7505", + "post_count": 12 + }, + { + "id": 21269, + "name": "Seph_\u0026_Dom:_The_Return_by_Naughtymorg", + "created_at": "2020-08-06T17:48:05.224-04:00", + "updated_at": "2023-10-23T18:34:17.955-04:00", + "creator_id": 495401, + "description": "Twin brothers, Seph \u0026 Dom, return in an all new adventure! Idilic home life is interupted when a figure from their past returns to haunt them, but in the mean time that have more practical problems to deal with...\r\n\r\nShortcuts:\r\nChapter 1: \"A dark and snow night\":/posts/2350784\r\nChapter 2: \"The return\":/posts/2473201\r\nChapter 3: \"The farm\":/posts/2522054\r\nChapter 4: \"The mountain\":/posts/3229731\r\nChapter 5: \"Abduction\":/posts/3446921\r\nChapter 6: \"Another place\":/posts/4279303\r\nChapter 7: \"Home\":/posts/4353892\r\n\r\n\"First story\":https://e621.net/pools/12769", + "is_active": true, + "category": "series", + "post_ids": [ + 2348355, + 2350784, + 2350793, + 2357193, + 2359787, + 2366391, + 2368974, + 2375379, + 2378177, + 2384589, + 2387005, + 2393632, + 2396230, + 2402491, + 2404815, + 2411209, + 2413657, + 2418493, + 2422030, + 2428010, + 2437146, + 2437149, + 2439622, + 2448506, + 2454703, + 2457274, + 2463769, + 2466335, + 2473201, + 2473206, + 2475767, + 2483905, + 2485177, + 2491738, + 2494566, + 2501527, + 2504178, + 2511398, + 2514055, + 2520868, + 2522054, + 2522069, + 2522080, + 2522083, + 2522090, + 2522097, + 2522109, + 2522111, + 2522115, + 2522121, + 2582599, + 2586885, + 2597781, + 2603783, + 2613664, + 2624435, + 2635775, + 2642189, + 2647639, + 2651617, + 2661619, + 2671322, + 2676038, + 2683174, + 2691208, + 2699825, + 2710960, + 2720660, + 2726986, + 2735196, + 2740166, + 2748756, + 2756407, + 2768971, + 2783278, + 2793639, + 2801997, + 2811468, + 2820844, + 2825225, + 2832777, + 2838707, + 2846611, + 2851700, + 2864392, + 2872632, + 2878673, + 2886271, + 2895922, + 2908013, + 2919414, + 2927252, + 2937879, + 2947391, + 2957455, + 2965618, + 2974706, + 2985554, + 2991719, + 3000023, + 3009413, + 3022280, + 3032414, + 3043513, + 3055027, + 3065405, + 3075770, + 3086814, + 3098788, + 3111271, + 3121660, + 3133343, + 3148243, + 3170786, + 3183003, + 3193065, + 3199501, + 3206434, + 3229731, + 3236498, + 3241492, + 3253886, + 3267662, + 3278952, + 3292092, + 3306211, + 3319059, + 3330944, + 3343357, + 3368407, + 3372896, + 3383039, + 3395389, + 3409114, + 3420599, + 3446917, + 3446921, + 3446938, + 3446968, + 3450127, + 3460404, + 3473193, + 3487309, + 3500983, + 3514446, + 3529224, + 3542304, + 3555807, + 3568960, + 3582702, + 3596360, + 3611013, + 3625315, + 3639882, + 3654966, + 3670087, + 3684381, + 3696906, + 3712415, + 3725328, + 3740286, + 3754006, + 3767143, + 3780382, + 3797392, + 3808129, + 3822233, + 3837491, + 3850198, + 3863715, + 3877127, + 3891563, + 3906431, + 3919936, + 3935901, + 3949147, + 3962216, + 3976481, + 3991601, + 4008431, + 4021641, + 4032570, + 4046602, + 4060843, + 4082015, + 4086712, + 4099355, + 4114735, + 4126605, + 4140736, + 4154827, + 4168851, + 4184906, + 4198564, + 4211653, + 4225591, + 4225613, + 4279303, + 4279351, + 4280604, + 4280950, + 4286588, + 4291390, + 4299332, + 4302936, + 4306827, + 4317301, + 4326068, + 4333969, + 4344111, + 4348250, + 4348295, + 4353892, + 4354817, + 4361120, + 4369905 + ], + "creator_name": "lurkmore", + "post_count": 216 + }, + { + "id": 37143, + "name": "Black_kitten_mornings", + "created_at": "2023-10-23T18:07:06.285-04:00", + "updated_at": "2023-10-23T18:33:22.947-04:00", + "creator_id": 1247261, + "description": "All morning comics by black kitten", + "is_active": true, + "category": "series", + "post_ids": [ + 1140266, + 1140426, + 1140668, + 1140749, + 1141758, + 3026764, + 1141986, + 1143754, + 1143799, + 1144679, + 1145566, + 1146248, + 1146341, + 1147334, + 1152903, + 1155412, + 1156333, + 1157938, + 1157983, + 1157987, + 2095024, + 2095082, + 2095156, + 2095718, + 2096142, + 2096951, + 2097969, + 2099326, + 2100527, + 2101703, + 2102692, + 2103850, + 2104931, + 2106009, + 2107066, + 2108134, + 2109144, + 2110079, + 2111120, + 2111995, + 2113043, + 2114235, + 2115401 + ], + "creator_name": "bestponydash", + "post_count": 43 + }, + { + "id": 37079, + "name": "Foxdale_Frights_2023", + "created_at": "2023-10-20T09:47:34.181-04:00", + "updated_at": "2023-10-23T18:22:13.442-04:00", + "creator_id": 340507, + "description": "A non-canon Halloween comic with the characters of Foxdale by TheKbear, written by MonkeyDBax", + "is_active": true, + "category": "series", + "post_ids": [ + 4359757, + 4361708, + 4363868, + 4365810, + 4367907, + 4369753 + ], + "creator_name": "Thekinkybear", + "post_count": 6 + }, + { + "id": 35414, + "name": "Force_of_Nature_-_Chapter_2", + "created_at": "2023-07-24T11:18:05.876-04:00", + "updated_at": "2023-10-23T18:08:38.844-04:00", + "creator_id": 634146, + "description": "Comic by {{Dr.Bubblebum}}\r\n\r\n\"Full Series\":/pools/30365\r\n\"Chapter 1\":/pools/35413\r\n[b]Chapter 2[/b]\r\n", + "is_active": true, + "category": "series", + "post_ids": [ + 4052905, + 4058753, + 4067168, + 4072693, + 4080468, + 4086691, + 4093271, + 4098930, + 4105904, + 4113769, + 4119206, + 4133894, + 4147618, + 4162406, + 4176347, + 4190706, + 4204959, + 4219555, + 4232607, + 4246347, + 4259804, + 4272524, + 4310477, + 4327430, + 4342080, + 4355294, + 4369724 + ], + "creator_name": "RustyPup", + "post_count": 27 + }, + { + "id": 36258, + "name": "Kinktober_2023_by_MaiTeik", + "created_at": "2023-09-12T14:43:34.631-04:00", + "updated_at": "2023-10-23T17:26:19.418-04:00", + "creator_id": 965016, + "description": "", + "is_active": true, + "category": "collection", + "post_ids": [ + 4287969, + 4299647, + 4303469, + 4307218, + 4355954, + 4357885, + 4357886, + 4359559, + 4369804 + ], + "creator_name": "MaiTeik", + "post_count": 9 + }, + { + "id": 18153, + "name": "Oversexed_Evolutions:_The_Comic_(kuroodod)", + "created_at": "2019-08-26T17:01:28.505-04:00", + "updated_at": "2023-10-23T17:17:55.088-04:00", + "creator_id": 317583, + "description": "Welcome to a grand, oversexed adventure! In this series Sam and the gang are guild mates! New setting, new forms and the same gang of oversexed, fun loving friends. This is a slice of life, adult, comedy web comic featuring Sam as a gorgeous fennec fox.\r\n\r\nA day in the life of Sam. He returns from a lengthy mission out of town. Accompanied by his friend and new character, Alan. A large western dragon. Alan heads off for a breather after all the flying about as Sam heads for the main hall of the guild. A new tale begins!\r\n\r\nhttp://www.patreon.com/kuroodod", + "is_active": true, + "category": "series", + "post_ids": [ + 1974429, + 1981129, + 1987878, + 1994954, + 2002059, + 2008762, + 2015872, + 2023022, + 2030190, + 2036829, + 2044177, + 2051204, + 2058430, + 2065641, + 2072747, + 2079255, + 2086335, + 2093453, + 2100854, + 2108453, + 2115812, + 2123369, + 2130900, + 2138172, + 2145929, + 2153328, + 2160652, + 2168692, + 2175042, + 2181900, + 2190077, + 2197752, + 2205606, + 2215171, + 2224455, + 2233327, + 2242644, + 2252445, + 2261805, + 2270263, + 2278912, + 2287340, + 2295289, + 2303898, + 2312664, + 2321222, + 2329234, + 2338059, + 2347272, + 2356221, + 2365386, + 2374436, + 2383780, + 2392561, + 2401710, + 2410246, + 2418666, + 2427101, + 2436021, + 2445235, + 2453702, + 2462497, + 2472143, + 2481417, + 2490611, + 2500400, + 2510247, + 2519821, + 2528935, + 2537710, + 2548109, + 2558131, + 2567354, + 2577254, + 2587167, + 2596867, + 2606744, + 2616728, + 2626023, + 2635924, + 2645317, + 2655284, + 2665219, + 2674426, + 2684593, + 2695124, + 2704799, + 2714252, + 2724196, + 2734061, + 2743631, + 2754127, + 2764028, + 2775027, + 2785286, + 2795834, + 2806166, + 2816903, + 2827410, + 2837782, + 2848742, + 2860284, + 2871476, + 2882360, + 2893407, + 2904121, + 2915072, + 2926238, + 2936811, + 2947906, + 2959678, + 2970784, + 2981454, + 2992329, + 3004555, + 3016012, + 3027159, + 3038834, + 3049959, + 3060620, + 3070949, + 3082380, + 3093194, + 3104659, + 3116871, + 3128778, + 3140470, + 3152134, + 3164406, + 3176880, + 3189068, + 3200173, + 3212237, + 3224172, + 3235284, + 3247592, + 3260248, + 3273117, + 3286732, + 3299825, + 3312809, + 3325747, + 3338060, + 3350480, + 3363139, + 3376786, + 3390005, + 3402098, + 3415145, + 3428300, + 3441278, + 3454688, + 3467630, + 3480846, + 3495414, + 3508735, + 3522170, + 3535873, + 3549875, + 3563137, + 3576817, + 3590266, + 3604185, + 3618895, + 3633604, + 3647449, + 3661975, + 3677550, + 3691458, + 3705703, + 3719257, + 3733575, + 3747207, + 3760088, + 3773949, + 3787831, + 3802421, + 3816036, + 3829568, + 3844122, + 3857705, + 3871080, + 3884981, + 3899280, + 3912639, + 3927907, + 3942634, + 3956799, + 3970144, + 3984557, + 3998256, + 4012279, + 4026580, + 4040850, + 4053846, + 4067165, + 4080524, + 4093808, + 4106820, + 4120092, + 4134862, + 4148396, + 4162580, + 4176840, + 4190884, + 4205373, + 4219776, + 4233145, + 4246752, + 4259942, + 4273067, + 4286497, + 4299871, + 4313712, + 4328225, + 4342927, + 4356303, + 4369782 + ], + "creator_name": "CosmicMewtwo", + "post_count": 218 + }, + { + "id": 37107, + "name": "Roona_Drive", + "created_at": "2023-10-21T15:43:25.140-04:00", + "updated_at": "2023-10-23T17:07:17.864-04:00", + "creator_id": 1184249, + "description": "", + "is_active": true, + "category": "collection", + "post_ids": [ + 4361787, + 4363532, + 4365307, + 4367181, + 4369692 + ], + "creator_name": "calvyyn", + "post_count": 5 + }, + { + "id": 36815, + "name": "Tavern_Bindii_Sprout:_the_most_delicious_dishes_for_everyone!_by_ripushko", + "created_at": "2023-10-07T08:44:59.775-04:00", + "updated_at": "2023-10-23T17:06:14.561-04:00", + "creator_id": 52800, + "description": "", + "is_active": true, + "category": "series", + "post_ids": [ + 4337757, + 4337758, + 4337761, + 4337763, + 4337764, + 4337767, + 4337768, + 4337775, + 4338317, + 4340453, + 4342373, + 4344581, + 4348464, + 4348465, + 4348466, + 4350529, + 4352299, + 4354315, + 4356238, + 4357896, + 4361628, + 4361629, + 4363805, + 4367845, + 4367846, + 4369761, + 4369762 + ], + "creator_name": "Suki_Lane", + "post_count": 27 + }, + { + "id": 30857, + "name": "Welcome_Home", + "created_at": "2022-10-10T14:34:53.488-04:00", + "updated_at": "2023-10-23T16:42:20.670-04:00", + "creator_id": 420111, + "description": "Preceeded by these two:\r\n* \"DV's Camwhore Adventure\":/pools/20521\r\n* \"Welcome to Africa\":/pools/25051\r\nThe overall story is a little disjointed since I've always used long comics for general practice rather than having every single one pre-planned. ", + "is_active": true, + "category": "series", + "post_ids": [ + 3618554, + 3633390, + 3647150, + 3661783, + 3677097, + 3691055, + 3699029, + 3705509, + 3719042, + 3733622, + 3741701, + 3747138, + 3760146, + 3773891, + 3787472, + 3801911, + 3816029, + 3829319, + 3844283, + 3851690, + 3857936, + 3871323, + 3885068, + 3898966, + 3912457, + 3921681, + 3928354, + 3942576, + 3956834, + 3970504, + 3984185, + 3998388, + 4012431, + 4026596, + 4041102, + 4053828, + 4067107, + 4080300, + 4093779, + 4100588, + 4106763, + 4134836, + 4148736, + 4162662, + 4176978, + 4190712, + 4204969, + 4219632, + 4233034, + 4246681, + 4259921, + 4273325, + 4286284, + 4293752, + 4299653, + 4307295, + 4313531, + 4328068, + 4342981, + 4356156, + 4369723 + ], + "creator_name": "TrashBadger", + "post_count": 61 + }, + { + "id": 35624, + "name": "Thanks_For_the_Ride_by_RosyCoyote", + "created_at": "2023-08-05T20:42:19.979-04:00", + "updated_at": "2023-10-23T16:41:47.634-04:00", + "creator_id": 160505, + "description": "", + "is_active": true, + "category": "series", + "post_ids": [ + 3972869, + 4028146, + 4215098, + 4215158, + 4303135, + 4326892, + 4369725 + ], + "creator_name": "Cinder", + "post_count": 7 + }, + { + "id": 30365, + "name": "Force_of_Nature_(Dr.Bubblebum)", + "created_at": "2022-09-08T04:02:48.164-04:00", + "updated_at": "2023-10-23T16:40:13.507-04:00", + "creator_id": 740513, + "description": "Comic by {{Dr.Bubblebum}}\r\n\r\n[b]Full Series[/b]\r\n\"Chapter 1\":/pools/35413\r\n\"Chapter 2\":/pools/35414\r\n", + "is_active": true, + "category": "series", + "post_ids": [ + 3554584, + 3554594, + 3554600, + 3554606, + 3554611, + 3554613, + 3563255, + 3563262, + 3575817, + 3575820, + 3589239, + 3589601, + 3603756, + 3617727, + 3633101, + 3646643, + 3661106, + 3676821, + 3690918, + 3705562, + 3718762, + 3733055, + 3746325, + 3759213, + 3787508, + 3802295, + 3814982, + 3827523, + 3843811, + 3856863, + 3870389, + 3884160, + 3927590, + 3941858, + 3955878, + 3970050, + 3984201, + 3997305, + 4011503, + 4026341, + 4040769, + 4052905, + 4058753, + 4067168, + 4072693, + 4080468, + 4086691, + 4093271, + 4098930, + 4105904, + 4113769, + 4119206, + 4133894, + 4147618, + 4162406, + 4176347, + 4190706, + 4204959, + 4219555, + 4232607, + 4246347, + 4259804, + 4272524, + 4310477, + 4327430, + 4342080, + 4355294, + 4369724 + ], + "creator_name": "DocBubble", + "post_count": 68 + }, + { + "id": 37142, + "name": "Office_Fox_Training_by_ehrrr", + "created_at": "2023-10-23T16:13:01.791-04:00", + "updated_at": "2023-10-23T16:13:01.791-04:00", + "creator_id": 602924, + "description": "", + "is_active": true, + "category": "series", + "post_ids": [ + 4369687, + 4369688, + 4369689, + 4369690 + ], + "creator_name": "DemonTheDarkHound", + "post_count": 4 + }, + { + "id": 37141, + "name": "Comic_by_privvys-art", + "created_at": "2023-10-23T15:42:20.481-04:00", + "updated_at": "2023-10-23T15:47:40.970-04:00", + "creator_id": 590493, + "description": "", + "is_active": true, + "category": "series", + "post_ids": [ + 4215833, + 4215840, + 4215850, + 4215858, + 4215866 + ], + "creator_name": "Cum_dump01", + "post_count": 5 + }, + { + "id": 28796, + "name": "Be_Wary_of_Wolf", + "created_at": "2022-05-28T16:10:18.599-04:00", + "updated_at": "2023-10-23T15:38:45.487-04:00", + "creator_id": 224736, + "description": "A rule 34 comic featuring Blaidd, the Tarnished, and his Mimic Tear.\r\n\r\nThe entire series is uncompressed and available at the artist's Itaku gallery:\r\nhttps://itaku.ee/profile/dangpa/gallery/13280", + "is_active": false, + "category": "series", + "post_ids": [ + 3428829, + 3428833, + 3428835, + 3428838, + 3428841, + 3435281, + 3435282, + 3435283, + 3684781, + 3684783, + 3684786 + ], + "creator_name": "Acron", + "post_count": 11 + }, + { + "id": 36957, + "name": "MLP:His_Nocturnal_Excellency", + "created_at": "2023-10-13T19:24:00.576-04:00", + "updated_at": "2023-10-23T15:36:58.239-04:00", + "creator_id": 16883, + "description": "Two (un)lucky royal guards who saw something they didn’t have to~", + "is_active": true, + "category": "series", + "post_ids": [ + 4350801, + 4350833, + 4351058, + 4351880, + 4351891, + 4351894, + 4351915, + 4352000, + 4352079, + 4352555, + 4360920, + 4369661 + ], + "creator_name": "2DUK", + "post_count": 12 + }, + { + "id": 13304, + "name": "Kadath_-_Ask_Puz_n_Pals", + "created_at": "2017-12-23T17:38:23.452-05:00", + "updated_at": "2023-10-23T15:26:52.861-04:00", + "creator_id": 203967, + "description": "", + "is_active": true, + "category": "series", + "post_ids": [ + 630238, + 1676438, + 631170, + 693417, + 662360, + 617234, + 1054739, + 1676449, + 662490, + 1676444, + 668165, + 644272, + 1676451, + 1676454, + 652245, + 658322, + 662361, + 670037, + 1044857, + 1676455, + 1676457, + 1676463, + 692806, + 1481082, + 1676465, + 703742, + 708315, + 1676468, + 1676469, + 1676483, + 1676484, + 1676486, + 1054787, + 1676487, + 1177867, + 1676490, + 1676498, + 1046784, + 1676520, + 1159133, + 995647, + 995651, + 1684234, + 1684238, + 1025425, + 1684241, + 1684247, + 1684251, + 1684253, + 1684255, + 1182391, + 1684261, + 1684263, + 1684265, + 956785, + 856642, + 1684575, + 1684580, + 1041630, + 1684590, + 1684623, + 890461, + 1699763, + 1699765, + 1699886, + 1043104, + 1040277, + 1702763, + 1041350, + 1154753, + 975681, + 995672, + 1702778, + 967978, + 1166911, + 976819, + 983444, + 989250, + 995728, + 1006204, + 1009352, + 1023109, + 1023860, + 1657853, + 1035174, + 1702783, + 1050508, + 1063272, + 1065401, + 1702794, + 1093011, + 1704675, + 1088971, + 1104950, + 1114944, + 1118059, + 1718587, + 1153012, + 1143667, + 1146782, + 1149454, + 1157548, + 1718590, + 1169545, + 1658087, + 1187797, + 1194651, + 1210745, + 1200722, + 1718595, + 1235989, + 1718598, + 1718607, + 1238578, + 1304985, + 1485692, + 2462750, + 1718611, + 1275232, + 1718613, + 1286601, + 1307406, + 1718615, + 1313496, + 1718617, + 1332493, + 1718626, + 1375822, + 1718632, + 1366967, + 1718637, + 1718656, + 1367897, + 1718731, + 1718743, + 1718746, + 1718752, + 1718753, + 1718755, + 1718756, + 1407985, + 1421690, + 1427492, + 1434167, + 1441546, + 1450270, + 1456736, + 1462732, + 1718759, + 1475189, + 1481348, + 1489421, + 1504842, + 1510773, + 1517731, + 1525202, + 1531200, + 1540823, + 1545650, + 1554270, + 1578107, + 1578114, + 1578117, + 1578122, + 1584674, + 1718765, + 1719539, + 1607297, + 1627041, + 1619706, + 1625710, + 1719549, + 1637714, + 1650509, + 1665433, + 1663060, + 1666032, + 1674763, + 1682759, + 1688129, + 1692567, + 1699733, + 1710614, + 1712683, + 1723457, + 1732693, + 1739101, + 1746646, + 1753672, + 1759242, + 1765706, + 1774123, + 1778077, + 1796713, + 1796719, + 1796721, + 1801950, + 1808153, + 1817826, + 1821656, + 1828742, + 1841110, + 1845006, + 1852914, + 1858829, + 1866294, + 1872379, + 1879013, + 1901733, + 1899225, + 1905899, + 1913189, + 1918600, + 1928299, + 1934134, + 1940645, + 1955049, + 1955062, + 1969243, + 1969244, + 1974958, + 1990873, + 1995972, + 2055266, + 2055295, + 2015840, + 2030557, + 2030561, + 2036791, + 2050575, + 2051306, + 2058425, + 2065735, + 2079218, + 2085974, + 2093182, + 2100739, + 2108430, + 2115520, + 2123242, + 2130622, + 2141496, + 2145741, + 2153145, + 2160419, + 2175105, + 2181771, + 2189834, + 2197604, + 2205520, + 2455094, + 2224412, + 2233529, + 2346806, + 2252511, + 2261734, + 2270181, + 2288028, + 2295233, + 2303886, + 2346815, + 2329281, + 2329282, + 2455064, + 2347388, + 2374311, + 2375240, + 2455046, + 2392530, + 2401574, + 2455032, + 2418673, + 2427046, + 2436081, + 2454996, + 2453609, + 2462595, + 2481552, + 2490754, + 2500728, + 2510319, + 2520028, + 2528785, + 2537996, + 2548380, + 2557938, + 2568117, + 2577143, + 2587885, + 2606706, + 2620104, + 2626026, + 2639554, + 2649081, + 2655403, + 2665204, + 2674536, + 2685340, + 2695203, + 2704822, + 2714255, + 2734062, + 2743451, + 2753887, + 2764016, + 2774919, + 2785229, + 2795770, + 2805954, + 2816684, + 2827335, + 2837467, + 2848588, + 2871165, + 2882243, + 2893047, + 2903994, + 2914850, + 2926177, + 2936725, + 2947748, + 2959603, + 2970624, + 2981141, + 2992220, + 3015920, + 3027003, + 3038686, + 3049864, + 3060970, + 3070488, + 3082033, + 3092901, + 3136925, + 3128448, + 3140008, + 3151748, + 3163800, + 3179944, + 3188621, + 3199792, + 3211923, + 3223798, + 3235024, + 3247417, + 3260462, + 3272796, + 3286483, + 3299598, + 3325089, + 3337711, + 3350193, + 3362858, + 3376540, + 3389695, + 3402000, + 3414857, + 3428622, + 3441017, + 3454406, + 3467494, + 3494840, + 3508345, + 3521952, + 3535653, + 3549598, + 3562968, + 3576579, + 3589869, + 3618629, + 3633417, + 3647311, + 3661904, + 3677023, + 3691102, + 3705470, + 3718951, + 3733564, + 3747049, + 3759863, + 3773761, + 3802434, + 3815638, + 3829380, + 3843904, + 3857615, + 3870883, + 3884628, + 3898856, + 3912428, + 3927934, + 3943098, + 3956606, + 3984320, + 3997990, + 4012126, + 4026439, + 4040736, + 4053623, + 4067036, + 4080410, + 4094237, + 4106647, + 4119791, + 4134571, + 4147856, + 4162221, + 4176668, + 4190630, + 4219619, + 4232834, + 4246562, + 4259668, + 4272879, + 4286245, + 4300368, + 4313492, + 4342647, + 4355962, + 4369646 + ], + "creator_name": "zenitix", + "post_count": 420 + }, + { + "id": 29999, + "name": "Vixen_Logic_by_Foxboy83_and_Tootaloo", + "created_at": "2022-08-15T19:34:28.637-04:00", + "updated_at": "2023-10-23T15:24:33.542-04:00", + "creator_id": 407542, + "description": "thumb #3509009\r\nA webcomic by [[tootaloo]] and [[foxboy83]]. It’s a slice of life comic about vixens set in the sunny city of Calina !\r\n\r\nUpdates on Mondays on \"vixenlogic.com\":https://www.vixenlogic.com/.\r\nRead from the first page \"here\":https://www.vixenlogic.com/vl0001/.\r\n\r\nThe comic will always be free to read, but if you would like to support the creators, please consider joining \"Patreon\":https://www.patreon.com/foxboy83 or send a tip through \"Ko-fi\":https://ko-fi.com/foxboy83. If you feel so inclined, you can also help support by donating directly through \"PayPal\":https://www.paypal.com/paypalme/foxboy83arts. Thank you for your support and readership!\r\n\r\n\"e621 wiki page\":/wiki_pages/46136", + "is_active": true, + "category": "series", + "post_ids": [ + 3509009, + 3508448, + 3508483, + 3508487, + 3508512, + 3508528, + 3508541, + 3508552, + 3508583, + 3522061, + 3536333, + 3549583, + 3562987, + 3576580, + 3589784, + 3603857, + 3618726, + 3633369, + 3647440, + 3661821, + 3676830, + 3691167, + 3705539, + 3719342, + 3733458, + 3746728, + 3759751, + 3802160, + 3816074, + 3829081, + 3862973, + 3885395, + 3898635, + 3913009, + 3928328, + 3942109, + 3956661, + 3970712, + 4011831, + 4026851, + 4040447, + 4053370, + 4066692, + 4080185, + 4106795, + 4120367, + 4134647, + 4164757, + 4178246, + 4190538, + 4209464, + 4232922, + 4246567, + 4265376, + 4313703, + 4328047, + 4342974, + 4355944, + 4369644 + ], + "creator_name": "Primonyr", + "post_count": 59 + }, + { + "id": 20016, + "name": "Striped_Sins", + "created_at": "2020-03-21T21:02:18.847-04:00", + "updated_at": "2023-10-23T15:12:20.759-04:00", + "creator_id": 203267, + "description": "A cute incest story between raccoon siblings, made by Willitfit.", + "is_active": true, + "category": "series", + "post_ids": [ + 2187821, + 2187831, + 2221489, + 2238278, + 2261852, + 2293979, + 2296126, + 2342775, + 2344099, + 2394950, + 2395139, + 2425517, + 2426992, + 2429253, + 2456119, + 2458303, + 2504368, + 2505713, + 2509082, + 2552325, + 2558126, + 2561857, + 2576112, + 2579918, + 2584217, + 2584234, + 2595243, + 2595266, + 2595285, + 2611996, + 2612020, + 2628724, + 2647947, + 2647955, + 2650498, + 2648021, + 2670047, + 2673340, + 2684251, + 2722588, + 2723954, + 2726868, + 2729804, + 2740187, + 2742149, + 2755307, + 2768402, + 2768403, + 2786053, + 2787649, + 2796738, + 2811212, + 2826418, + 2838524, + 2860081, + 2861661, + 2863143, + 2888131, + 2892345, + 2906919, + 2908227, + 2931213, + 2936585, + 2960632, + 2970045, + 3007778, + 3010234, + 3026748, + 3028646, + 3031425, + 3037302, + 3054686, + 3083417, + 3084869, + 3086830, + 3089758, + 3107289, + 3119704, + 3128447, + 3145084, + 3157044, + 3171128, + 3177913, + 3188673, + 3188682, + 3201265, + 3215163, + 3228641, + 3259646, + 3262044, + 3263832, + 3288307, + 3289942, + 3311830, + 3377964, + 3380437, + 3392855, + 3396864, + 3429027, + 3432686, + 3436971, + 3440350, + 3501628, + 3514099, + 3553654, + 3564145, + 3576200, + 3589310, + 3632587, + 3637061, + 3638866, + 3663741, + 3665600, + 3676018, + 3705135, + 3705310, + 3732541, + 3754258, + 3761782, + 3784981, + 3784982, + 3802227, + 3815123, + 3829189, + 3849602, + 3859197, + 3902484, + 3904416, + 3905802, + 3912079, + 3913796, + 3944199, + 3944207, + 3972072, + 3979166, + 3988059, + 4002063, + 4027635, + 4029658, + 4042455, + 4053199, + 4066532, + 4087405, + 4095489, + 4108112, + 4119443, + 4148185, + 4161601, + 4176396, + 4206438, + 4207181, + 4232830, + 4238754, + 4249982, + 4267335, + 4291453, + 4304913, + 4304920, + 4327483, + 4327522, + 4344389, + 4355921, + 4369623 + ], + "creator_name": "willitfit", + "post_count": 163 + }, + { + "id": 30925, + "name": "Bai_Se's_Locktober_2022", + "created_at": "2022-10-14T22:04:17.169-04:00", + "updated_at": "2023-10-23T15:07:46.322-04:00", + "creator_id": 346509, + "description": "Drawn by Aennor.\r\n\r\nSequel here: https://e621.net/pools/36782", + "is_active": true, + "category": "series", + "post_ids": [ + 3627374, + 3627382, + 3627391, + 3627401, + 3627415, + 3627426, + 3627431, + 3627437, + 3627444, + 3629380, + 3631707, + 3633747, + 3636812, + 3637794, + 3639441, + 3642348, + 3644705, + 3646044, + 3647446, + 3650906, + 3652067, + 3662276, + 3674447, + 3674449 + ], + "creator_name": "'-'", + "post_count": 24 + }, + { + "id": 36782, + "name": "Bai_Se's_Locktober_2", + "created_at": "2023-10-05T18:13:41.403-04:00", + "updated_at": "2023-10-23T15:07:23.510-04:00", + "creator_id": 48728, + "description": "Comic done for @AennorI on twitter for Locktober. \r\n\r\nSequel to: https://e621.net/pools/30925", + "is_active": true, + "category": "series", + "post_ids": [ + 4334678, + 4334694, + 4338527, + 4338540, + 4341881, + 4341754, + 4341753, + 4345843, + 4346814, + 4359899, + 4365788 + ], + "creator_name": "Aennor", + "post_count": 11 + }, + { + "id": 36613, + "name": "Confidence_boost_by_a0nmaster", + "created_at": "2023-09-30T11:41:18.436-04:00", + "updated_at": "2023-10-23T14:53:22.581-04:00", + "creator_id": 628038, + "description": "", + "is_active": true, + "category": "series", + "post_ids": [], + "creator_name": "Coatl", + "post_count": 0 + }, + { + "id": 37090, + "name": "Confidence_boost!_by_A0Nmaster", + "created_at": "2023-10-20T19:00:19.325-04:00", + "updated_at": "2023-10-23T14:48:50.582-04:00", + "creator_id": 308309, + "description": "small comic involving A0N and Iggy's relationship and a lot of growth...\r\nFor people into hyper, bellies and muscles!", + "is_active": true, + "category": "series", + "post_ids": [ + 4302994, + 4310955, + 4310966, + 4317402, + 4321660, + 4330078, + 4332868, + 4350724, + 4350738, + 4356475, + 4359945, + 4364056, + 4369580 + ], + "creator_name": "A0Nsecretroom", + "post_count": 13 + }, + { + "id": 34651, + "name": "Collateral_Charm", + "created_at": "2023-06-10T13:43:07.782-04:00", + "updated_at": "2023-10-23T14:45:44.438-04:00", + "creator_id": 1155202, + "description": "", + "is_active": true, + "category": "series", + "post_ids": [ + 4102576, + 4102577, + 4102578, + 4369579 + ], + "creator_name": "Lady_Frost", + "post_count": 4 + }, + { + "id": 36723, + "name": "Boldtober_2023_by_Spe", + "created_at": "2023-10-03T13:04:35.662-04:00", + "updated_at": "2023-10-23T14:25:26.203-04:00", + "creator_id": 752426, + "description": "https://linktr.ee/scaliespe", + "is_active": true, + "category": "collection", + "post_ids": [ + 4327948, + 4327954, + 4329758, + 4329759, + 4330317, + 4330345, + 4330385, + 4330419, + 4331994, + 4338517, + 4338644, + 4369559 + ], + "creator_name": "scaliespe", + "post_count": 12 + }, + { + "id": 37033, + "name": "Mr.Wolf_in_Prison", + "created_at": "2023-10-17T14:19:33.498-04:00", + "updated_at": "2023-10-23T14:22:12.880-04:00", + "creator_id": 1497740, + "description": "", + "is_active": true, + "category": "series", + "post_ids": [ + 4313323, + 4327952, + 4342486, + 4355919, + 4369549 + ], + "creator_name": "mafia_3_enjoyer", + "post_count": 5 + }, + { + "id": 29956, + "name": "MLP:_The_Long_Way_Back", + "created_at": "2022-08-12T14:04:00.426-04:00", + "updated_at": "2023-10-23T13:32:50.204-04:00", + "creator_id": 16883, + "description": "", + "is_active": true, + "category": "series", + "post_ids": [ + 3423050, + 3502160, + 3519878, + 3616922, + 3616925, + 3703433, + 3703441, + 3703004, + 3753664, + 3783499, + 3901518, + 3924246, + 4026997, + 4049891, + 4102671, + 4120017, + 4159825, + 4174455, + 4229058, + 4248655, + 4369468 + ], + "creator_name": "2DUK", + "post_count": 21 + }, + { + "id": 37140, + "name": "Darra_and_Zuki_-_Pet_Gargoyle", + "created_at": "2023-10-23T13:04:42.049-04:00", + "updated_at": "2023-10-23T13:13:36.277-04:00", + "creator_id": 776765, + "description": "", + "is_active": true, + "category": "series", + "post_ids": [ + 4369406, + 4369424, + 4369436, + 4369440, + 4369442 + ], + "creator_name": "Derresh", + "post_count": 5 + }, + { + "id": 36727, + "name": "Kinktober_2023_by_Oro97", + "created_at": "2023-10-03T14:50:54.461-04:00", + "updated_at": "2023-10-23T13:06:05.153-04:00", + "creator_id": 336260, + "description": "", + "is_active": true, + "category": "collection", + "post_ids": [ + 4325892, + 4327834, + 4330147, + 4332037, + 4334230, + 4336148, + 4338282, + 4340389, + 4342568, + 4344236, + 4346323, + 4348111, + 4350069, + 4352357, + 4354293, + 4355866, + 4357627, + 4359450, + 4361415, + 4363516, + 4365364, + 4369404 + ], + "creator_name": "Coopercaller", + "post_count": 22 + }, + { + "id": 27309, + "name": "FUR_Magazine", + "created_at": "2022-02-03T18:28:44.903-05:00", + "updated_at": "2023-10-23T12:50:53.606-04:00", + "creator_id": 128792, + "description": "A collection of magazine covers drawn by Kurus, commissioned by LexiThotter", + "is_active": true, + "category": "collection", + "post_ids": [ + 3157276, + 3157293, + 3157302, + 3227218, + 3227228, + 3295758, + 3295765, + 3410970, + 3410973, + 4081291, + 4081295 + ], + "creator_name": "Kurus_Kururu", + "post_count": 11 + }, + { + "id": 37120, + "name": "Tf_cafe", + "created_at": "2023-10-22T06:00:40.209-04:00", + "updated_at": "2023-10-23T12:47:32.177-04:00", + "creator_id": 456309, + "description": "Tf cafe series 1\r\n\r\nhttps://www.furaffinity.net/gallery/narusewolf/folder/1324345/Tf-Cafe/\r\n\r\nhttps://www.patreon.com/Narusewolf", + "is_active": true, + "category": "series", + "post_ids": [ + 4366901, + 4369399 + ], + "creator_name": "NaruseWolf", + "post_count": 2 + }, + { + "id": 36720, + "name": "Passiontail_Isle_:_Share_Me_with_the_Other_Boys_by_Insomniacovrlrd", + "created_at": "2023-10-03T12:08:33.699-04:00", + "updated_at": "2023-10-23T12:31:47.348-04:00", + "creator_id": 934802, + "description": "Aurora can't stop thinking about her accidentally-intimate encounter with Charlie! She's got a great relationship with her boyfriend Blake, but the thrill of the unknown is too much to handle! But what does Blake think about all this? Maybe they can open things up for some fun...\r\n\r\nContains a juicy m/f/m threesome, knotting, deepthroating and a touch of electrostim,", + "is_active": true, + "category": "series", + "post_ids": [ + 4327967, + 4327980, + 4341358, + 4355890, + 4369257 + ], + "creator_name": "Benjiboyo", + "post_count": 5 + }, + { + "id": 36663, + "name": "Cocktober_Bone_by_Snowskau", + "created_at": "2023-10-02T06:45:46.112-04:00", + "updated_at": "2023-10-23T12:30:07.560-04:00", + "creator_id": 224736, + "description": "", + "is_active": true, + "category": "collection", + "post_ids": [ + 4325684, + 4327229, + 4329723, + 4331912, + 4333720, + 4335700, + 4337693, + 4340207, + 4342057, + 4344052, + 4345712, + 4347557, + 4349661, + 4352181, + 4353643, + 4357274, + 4359019, + 4360887, + 4363152, + 4365000, + 4367147, + 4369377 + ], + "creator_name": "Acron", + "post_count": 22 + }, + { + "id": 36638, + "name": "Kinktober_2023_(ReindeerViking)", + "created_at": "2023-10-01T11:53:33.670-04:00", + "updated_at": "2023-10-23T12:22:36.703-04:00", + "creator_id": 461163, + "description": "", + "is_active": true, + "category": "series", + "post_ids": [ + 4325484, + 4327626, + 4330273, + 4331933, + 4334075, + 4335867, + 4338014, + 4340176, + 4342223, + 4344117, + 4346267, + 4348242, + 4350422, + 4352017, + 4354001, + 4355713, + 4357485, + 4359254, + 4361188, + 4363262, + 4366123, + 4366125, + 4366126, + 4366128, + 4366129, + 4367414, + 4369365 + ], + "creator_name": "Alpha7505", + "post_count": 27 + }, + { + "id": 36649, + "name": "Anna's_2023_Giftober", + "created_at": "2023-10-01T17:55:42.893-04:00", + "updated_at": "2023-10-23T12:22:19.697-04:00", + "creator_id": 1402930, + "description": "An Inktober composed entirely and exclusively of giftart! With daily uploads to fill every single day of the month! Enjoy! ;3", + "is_active": true, + "category": "series", + "post_ids": [ + 4323228, + 4326248, + 4328295, + 4329830, + 4331960, + 4333831, + 4333835, + 4333843, + 4335907, + 4338059, + 4340185, + 4340195, + 4342111, + 4344552, + 4346307, + 4347756, + 4349714, + 4349735, + 4351833, + 4351837, + 4353647, + 4355853, + 4355865, + 4357348, + 4359286, + 4359290, + 4361091, + 4363520, + 4365535, + 4365543, + 4367519, + 4369364 + ], + "creator_name": "AnnaEngine", + "post_count": 32 + }, + { + "id": 36639, + "name": "Kinktober_2022_(ReindeerViking)", + "created_at": "2023-10-01T11:53:37.093-04:00", + "updated_at": "2023-10-23T12:22:17.111-04:00", + "creator_id": 461163, + "description": "", + "is_active": true, + "category": "series", + "post_ids": [ + 4325483, + 4327625, + 4330272, + 4331932, + 4334074, + 4335866, + 4338013, + 4340175, + 4342219, + 4344116, + 4346266, + 4348241, + 4350420, + 4352016, + 4354000, + 4355711, + 4357483, + 4324260, + 4361187, + 4324255, + 4366122, + 4367410, + 4369363 + ], + "creator_name": "Alpha7505", + "post_count": 23 + }, + { + "id": 36798, + "name": "From_another_World", + "created_at": "2023-10-06T07:24:11.215-04:00", + "updated_at": "2023-10-23T12:21:18.276-04:00", + "creator_id": 352523, + "description": "", + "is_active": true, + "category": "series", + "post_ids": [ + 4335662, + 4369356, + 4369362 + ], + "creator_name": "3dmodell", + "post_count": 3 + }, + { + "id": 31706, + "name": "Firecrest", + "created_at": "2022-12-07T12:29:34.088-05:00", + "updated_at": "2023-10-23T11:53:12.762-04:00", + "creator_id": 275616, + "description": "A comic by Corrsk.\r\n\r\nTweek Firecrest, a talented but solitary Skaven Engineer, is forced into a mission he is not prepared for. And the worst is yet to come.", + "is_active": true, + "category": "series", + "post_ids": [ + 3737384, + 3737407, + 3891231, + 3944138, + 3999934, + 4074308, + 4147735, + 4220959, + 4266814, + 4314822, + 4369328 + ], + "creator_name": "Corrsk", + "post_count": 11 + }, + { + "id": 36400, + "name": "Azrael/Azraelle_Staring_at_Nathile's/Natyna's_Chest", + "created_at": "2023-09-21T00:19:17.185-04:00", + "updated_at": "2023-10-23T11:52:52.842-04:00", + "creator_id": 1612135, + "description": "All the variations of the Girl Staring at Man's Chest meme featuring Nathile Tiduna and Azrael (Tertia). Female Names are Natyna = Nathile. Azraelle = Azrael.", + "is_active": false, + "category": "collection", + "post_ids": [ + 4304262, + 4304270, + 4304284, + 4304294, + 4304323, + 4304338, + 4304358, + 4304379 + ], + "creator_name": "SkyWater", + "post_count": 8 + }, + { + "id": 30150, + "name": "Coalt's_Patented_Pants_[Rimentus]", + "created_at": "2022-08-25T02:01:40.541-04:00", + "updated_at": "2023-10-23T11:24:41.728-04:00", + "creator_id": 523770, + "description": "[b]Coalt is on a vore-path.[/b]\r\n\r\nAll of the plot/vore choices have been done through \"Patreon\":https://www.patreon.com/dragoncoalt polls \u0026 the Discord server.\r\n\r\nShortcuts:\r\nChapter 1: Outcasts: Post #3526836\r\nThe Morning After (Chapter 1 Bonus): Post #3789055\r\nChapter 2: Magical Addiction: Post #3833272", + "is_active": true, + "category": "series", + "post_ids": [ + 3526826, + 3526836, + 3530439, + 3536389, + 3538192, + 3540484, + 3541300, + 3543202, + 3545063, + 3545965, + 3546878, + 3548060, + 3550358, + 3551891, + 3553976, + 3554983, + 3555877, + 3556948, + 3557878, + 3560419, + 3562509, + 3563497, + 3565102, + 3567356, + 3568164, + 3573225, + 3576001, + 3578042, + 3578815, + 3580791, + 3581678, + 3583345, + 3586295, + 3587366, + 3589345, + 3592528, + 3593775, + 3594658, + 3595603, + 3597382, + 3599150, + 3606146, + 3609118, + 3610257, + 3612896, + 3618241, + 3621268, + 3624358, + 3634098, + 3646946, + 3656922, + 3666022, + 3672058, + 3680617, + 3686527, + 3692479, + 3702647, + 3707018, + 3716532, + 3729803, + 3741139, + 3752192, + 3763384, + 3783060, + 3789055, + 3802667, + 3815175, + 3825247, + 3833272, + 3843503, + 3857991, + 3868644, + 3885257, + 3894613, + 3902276, + 3909741, + 3924218, + 3931534, + 3937953, + 3950137, + 3960534, + 3968156, + 3972452, + 3977269, + 3982228, + 3989579, + 3993460, + 3995428, + 4008495, + 4013709, + 4019693, + 4026519, + 4031885, + 4038076, + 4050193, + 4054310, + 4066650, + 4070474, + 4074982, + 4087833, + 4094071, + 4130029, + 4134968, + 4140039, + 4147032, + 4162021, + 4172797, + 4182256, + 4190273, + 4199312, + 4208791, + 4217364, + 4225643, + 4233260, + 4242935, + 4248133, + 4254316, + 4263218, + 4271297, + 4276642, + 4285842, + 4294878, + 4303271, + 4311145, + 4326037, + 4331931, + 4340651, + 4348172, + 4353877, + 4359289, + 4369277 + ], + "creator_name": "Bpedron", + "post_count": 131 + }, + { + "id": 37049, + "name": "[Lady_Dino]_6969", + "created_at": "2023-10-18T12:16:00.335-04:00", + "updated_at": "2023-10-23T11:21:24.899-04:00", + "creator_id": 230870, + "description": "A sequel to \"Alternative Training Methods\":https://e621.net/pools/33579", + "is_active": true, + "category": "series", + "post_ids": [ + 4113821, + 4359335 + ], + "creator_name": "MoonMoonMoon", + "post_count": 2 + }, + { + "id": 17689, + "name": "Street_Cubs", + "created_at": "2019-06-25T00:47:49.729-04:00", + "updated_at": "2023-10-23T11:02:17.510-04:00", + "creator_id": 147807, + "description": "Street Cubs by The Giant Hamster for The Super Professor. ", + "is_active": true, + "category": "series", + "post_ids": [ + 1838772, + 1913409, + 1913411, + 1913412, + 1913413, + 1913415, + 1953746, + 1967979, + 1977972, + 2114075, + 2117598, + 2154007, + 2650469, + 2661902, + 2671212, + 2685658, + 2698756, + 2707201, + 2716700, + 2729593, + 2748863, + 2762359, + 2773209, + 2779208, + 2792344, + 2804195, + 2819559, + 2830008, + 2835999, + 2850191, + 2894448, + 2858033, + 2919518, + 2883389, + 2869745, + 2931619, + 4152067, + 4190317, + 4176424, + 4211161, + 4225119, + 4253975, + 4282108, + 4293422, + 4307408, + 4325523, + 4344222, + 4357643, + 4369239 + ], + "creator_name": "Iago1", + "post_count": 49 + }, + { + "id": 34361, + "name": "Unusual_Vacation_5:_A_taste_of_a_new_world", + "created_at": "2023-05-24T16:09:57.981-04:00", + "updated_at": "2023-10-23T11:01:09.882-04:00", + "creator_id": 747313, + "description": "Part 1: \"Unusual Vacation: Hard Beginning\":/pools/23122\r\nPart 2: \"Unusual Vacation 2: What are friends for\":/pools/23170\r\nPart 3: \"Unusual Vacation 3: The wolf and the sheep\":/pools/24800\r\nPart 4: \"Unusual Vacation 4: Chasing with shadows\":/pools/28263\r\nPart 5: Unusual Vacation 5: A taste of a new world", + "is_active": true, + "category": "series", + "post_ids": [ + 4070928, + 4077820, + 4088232, + 4095510, + 4131734, + 4177776, + 4184313, + 4192745, + 4209334, + 4242139, + 4246755, + 4259578, + 4270302, + 4278857, + 4303404, + 4312149, + 4319298, + 4346313, + 4354226, + 4361223, + 4369237 + ], + "creator_name": "ConfusedRaven", + "post_count": 21 + } +] \ No newline at end of file diff --git a/tests/post.json b/tests/post.json new file mode 100644 index 0000000..fd373b4 --- /dev/null +++ b/tests/post.json @@ -0,0 +1,111 @@ +{ + "post": { + "id": 1337, + "created_at": "2007-02-23T22:14:42.048-05:00", + "updated_at": "2023-10-20T01:59:09.423-04:00", + "file": { + "width": 790, + "height": 748, + "ext": "jpg", + "size": 156917, + "md5": "4e7586f0666a7c735b2f9e1ccd18bc68", + "url": "https://static1.e621.net/data/4e/75/4e7586f0666a7c735b2f9e1ccd18bc68.jpg" + }, + "preview": { + "width": 150, + "height": 142, + "url": "https://static1.e621.net/data/preview/4e/75/4e7586f0666a7c735b2f9e1ccd18bc68.jpg" + }, + "sample": { + "has": false, + "height": 748, + "width": 790, + "url": "https://static1.e621.net/data/4e/75/4e7586f0666a7c735b2f9e1ccd18bc68.jpg", + "alternates": {} + }, + "score": { + "up": 46, + "down": -4, + "total": 42 + }, + "tags": { + "general": [ + "\u003c3", + "anthro", + "blush", + "bottomwear", + "briefs", + "chibi", + "clothed", + "clothing", + "dialogue", + "ear_piercing", + "footwear", + "funny_post_number", + "fur", + "group", + "hair", + "humor", + "legwear", + "male", + "pants", + "piercing", + "pink_hair", + "reluctant", + "shirt", + "simple_background", + "socks", + "text", + "topless", + "topwear", + "underwear" + ], + "artist": [ + "sneakerfox" + ], + "copyright": [], + "character": [], + "species": [ + "canid", + "canine", + "fox", + "mammal", + "mephitid", + "skunk" + ], + "invalid": [], + "meta": [ + "2003", + "english_text" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 42512291, + "flags": { + "pending": false, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "s", + "fav_count": 84, + "sources": [], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 17633, + "description": "", + "comment_count": 6, + "is_favorited": false, + "has_notes": false, + "duration": null + } +} \ No newline at end of file diff --git a/tests/posts.json b/tests/posts.json new file mode 100644 index 0000000..08d687a --- /dev/null +++ b/tests/posts.json @@ -0,0 +1,8820 @@ +{ + "posts": [ + { + "id": 4371101, + "created_at": "2023-10-24T09:39:49.752-04:00", + "updated_at": "2023-10-24T09:40:10.411-04:00", + "file": { + "width": 850, + "height": 1150, + "ext": "png", + "size": 853799, + "md5": "1fc3a105623ea8128148fd15123c6090", + "url": "https://static1.e621.net/data/1f/c3/1fc3a105623ea8128148fd15123c6090.png" + }, + "preview": { + "width": 110, + "height": 150, + "url": "https://static1.e621.net/data/preview/1f/c3/1fc3a105623ea8128148fd15123c6090.jpg" + }, + "sample": { + "has": false, + "height": 1150, + "width": 850, + "url": "https://static1.e621.net/data/1f/c3/1fc3a105623ea8128148fd15123c6090.png", + "alternates": {} + }, + "score": { + "up": 0, + "down": 0, + "total": 0 + }, + "tags": { + "general": [ + "3_toes", + "4_fingers", + "anthro", + "anthro_on_anthro", + "anthro_penetrated", + "anthro_penetrating", + "anthro_penetrating_anthro", + "areola", + "barefoot", + "bedroll", + "black_body", + "black_fur", + "black_hair", + "black_nose", + "bodily_fluids", + "border", + "bottomless", + "bottomless_anthro", + "bottomless_female", + "bottomwear", + "bottomwear_down", + "breasts", + "butt", + "claws", + "clitoral_hood", + "clitoris", + "clothed", + "clothing", + "clothing_lift", + "death_knight", + "duo", + "eyes_closed", + "face_lick", + "fangs", + "feet", + "female", + "female_penetrated", + "finger_claws", + "fingers", + "fur", + "genital_fluids", + "genitals", + "grey_body", + "grey_fur", + "hair", + "hindpaw", + "humanoid_hands", + "licking", + "lying", + "male", + "male/female", + "male_penetrating", + "male_penetrating_female", + "nipples", + "on_back", + "pants", + "pants_down", + "partially_clothed", + "paws", + "penetration", + "penile", + "penile_penetration", + "penis", + "penis_in_pussy", + "pink_hair", + "pussy", + "pussy_juice", + "pussy_juice_on_penis", + "sex", + "shirt", + "shirt_lift", + "smile", + "teeth", + "tent", + "toe_claws", + "toes", + "tongue", + "tongue_out", + "topwear", + "vaginal", + "vaginal_penetration", + "white_border" + ], + "artist": [ + "amocin" + ], + "copyright": [ + "blizzard_entertainment", + "druids_the_comic", + "warcraft" + ], + "character": [ + "callow_(amocin)", + "sicha_(amocin)" + ], + "species": [ + "canid", + "mammal", + "undead", + "were", + "werecanid", + "worgen" + ], + "invalid": [], + "meta": [ + "comic" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283588, + "flags": { + "pending": false, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "e", + "fav_count": 0, + "sources": [ + "https://www.furaffinity.net/view/54169525/", + "https://d.furaffinity.net/art/amocin/1698154050/1698154050.amocin_fftt37.png" + ], + "pools": [ + 29704 + ], + "relationships": { + "parent_id": null, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 461163, + "description": "", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371100, + "created_at": "2023-10-24T09:39:43.264-04:00", + "updated_at": "2023-10-24T09:40:10.404-04:00", + "file": { + "width": 850, + "height": 1150, + "ext": "png", + "size": 738422, + "md5": "2206594ec3969bd340912bdcf070d07b", + "url": "https://static1.e621.net/data/22/06/2206594ec3969bd340912bdcf070d07b.png" + }, + "preview": { + "width": 110, + "height": 150, + "url": "https://static1.e621.net/data/preview/22/06/2206594ec3969bd340912bdcf070d07b.jpg" + }, + "sample": { + "has": false, + "height": 1150, + "width": 850, + "url": "https://static1.e621.net/data/22/06/2206594ec3969bd340912bdcf070d07b.png", + "alternates": {} + }, + "score": { + "up": 0, + "down": 0, + "total": 0 + }, + "tags": { + "general": [ + "4_fingers", + "anthro", + "anthro_on_anthro", + "anthro_penetrated", + "anthro_penetrating", + "anthro_penetrating_anthro", + "areola", + "bedroll", + "black_body", + "black_fur", + "black_hair", + "black_nose", + "blue_eyes", + "bodily_fluids", + "border", + "bottomless", + "bottomless_anthro", + "bottomless_female", + "breasts", + "claws", + "clitoral_hood", + "clitoris", + "clothed", + "clothing", + "clothing_lift", + "death_knight", + "dialogue", + "duo", + "eyes_closed", + "fangs", + "female", + "female_penetrated", + "finger_claws", + "fingers", + "fur", + "genital_fluids", + "genitals", + "glowing", + "glowing_eyes", + "grey_body", + "grey_fur", + "hair", + "humanoid_hands", + "lying", + "male", + "male/female", + "male_penetrating", + "male_penetrating_female", + "nipples", + "on_back", + "penetration", + "penile", + "penile_penetration", + "penis", + "penis_in_pussy", + "pink_hair", + "pussy", + "pussy_juice", + "pussy_juice_on_penis", + "sex", + "shirt", + "shirt_lift", + "smile", + "speech_bubble", + "teeth", + "tent", + "tongue", + "topwear", + "vaginal", + "vaginal_penetration", + "white_border" + ], + "artist": [ + "amocin" + ], + "copyright": [ + "blizzard_entertainment", + "druids_the_comic", + "warcraft" + ], + "character": [ + "callow_(amocin)", + "sicha_(amocin)" + ], + "species": [ + "canid", + "mammal", + "undead", + "were", + "werecanid", + "worgen" + ], + "invalid": [], + "meta": [ + "comic" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283586, + "flags": { + "pending": false, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "e", + "fav_count": 0, + "sources": [ + "https://www.furaffinity.net/view/54169516/", + "https://d.furaffinity.net/art/amocin/1698153996/1698153996.amocin_fftt36.png" + ], + "pools": [ + 29704 + ], + "relationships": { + "parent_id": null, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 461163, + "description": "", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371099, + "created_at": "2023-10-24T09:37:24.456-04:00", + "updated_at": "2023-10-24T09:37:24.456-04:00", + "file": { + "width": 2800, + "height": 3500, + "ext": "jpg", + "size": 827524, + "md5": "da1a88508dabc637140fb33860402ef5", + "url": "https://static1.e621.net/data/da/1a/da1a88508dabc637140fb33860402ef5.jpg" + }, + "preview": { + "width": 120, + "height": 150, + "url": "https://static1.e621.net/data/preview/da/1a/da1a88508dabc637140fb33860402ef5.jpg" + }, + "sample": { + "has": true, + "height": 1062, + "width": 850, + "url": "https://static1.e621.net/data/sample/da/1a/da1a88508dabc637140fb33860402ef5.jpg", + "alternates": {} + }, + "score": { + "up": 0, + "down": 0, + "total": 0 + }, + "tags": { + "general": [ + "ambiguous_gender", + "anal", + "anal_penetration", + "animal_genitalia", + "animal_penis", + "anthro", + "anus", + "asphyxiation", + "balls", + "bodily_fluids", + "butt", + "canine_genitalia", + "canine_penis", + "choking", + "cum", + "cum_in_ass", + "cum_inside", + "duo", + "female", + "forest", + "forest_background", + "genital_fluids", + "genitals", + "halloween_theme", + "hindpaw", + "intersex", + "intersex/male", + "male", + "male/female", + "nature", + "nature_background", + "pawpads", + "paws", + "penetration", + "penis", + "plant", + "tree" + ], + "artist": [ + "nehocane" + ], + "copyright": [], + "character": [ + "ych_(character)" + ], + "species": [ + "canid", + "canine", + "mammal", + "were", + "werecanid", + "werecanine", + "werewolf" + ], + "invalid": [], + "meta": [ + "absurd_res", + "digital_drawing_(artwork)", + "digital_media_(artwork)", + "digital_painting_(artwork)", + "hi_res" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283572, + "flags": { + "pending": true, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "e", + "fav_count": 0, + "sources": [], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 1211527, + "description": "Since Halloween is coming up I thought I’d give everyone something to drool over. There’s a male and female version of the ych it is $30 no restrictions and it’s up on my dealers den.\r\nhttps://www.thedealersden.com/listing/werewolf-forest-ych/248024", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371098, + "created_at": "2023-10-24T09:36:58.016-04:00", + "updated_at": "2023-10-24T09:40:03.043-04:00", + "file": { + "width": 1396, + "height": 1600, + "ext": "jpg", + "size": 141793, + "md5": "f55aa56c1ba4ce335f523049d286b4be", + "url": "https://static1.e621.net/data/f5/5a/f55aa56c1ba4ce335f523049d286b4be.jpg" + }, + "preview": { + "width": 130, + "height": 150, + "url": "https://static1.e621.net/data/preview/f5/5a/f55aa56c1ba4ce335f523049d286b4be.jpg" + }, + "sample": { + "has": true, + "height": 974, + "width": 850, + "url": "https://static1.e621.net/data/sample/f5/5a/f55aa56c1ba4ce335f523049d286b4be.jpg", + "alternates": {} + }, + "score": { + "up": 2, + "down": 0, + "total": 2 + }, + "tags": { + "general": [ + "anthro", + "big_breasts", + "big_butt", + "breasts", + "butt", + "clothed", + "clothing", + "female", + "fur", + "huge_breasts", + "huge_butt", + "looking_at_viewer", + "looking_back", + "looking_back_at_viewer", + "mature_female", + "solo", + "thick_thighs", + "white_body", + "white_fur" + ], + "artist": [ + "berseepon09" + ], + "copyright": [ + "undertale", + "undertale_(series)" + ], + "character": [ + "toriel" + ], + "species": [ + "boss_monster", + "bovid", + "caprine", + "mammal" + ], + "invalid": [], + "meta": [ + "hi_res" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283570, + "flags": { + "pending": false, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "q", + "fav_count": 4, + "sources": [ + "https://twitter.com/Berseepon/status/1716810873191485622?t=K-3H7X--3x_8otSUsV3KQg\u0026s=19" + ], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 396514, + "description": "", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371097, + "created_at": "2023-10-24T09:35:55.232-04:00", + "updated_at": "2023-10-24T09:35:55.232-04:00", + "file": { + "width": 2800, + "height": 3500, + "ext": "jpg", + "size": 834071, + "md5": "a6b9b3ece6f1d0a1fd4aaf2f547f7c85", + "url": "https://static1.e621.net/data/a6/b9/a6b9b3ece6f1d0a1fd4aaf2f547f7c85.jpg" + }, + "preview": { + "width": 120, + "height": 150, + "url": "https://static1.e621.net/data/preview/a6/b9/a6b9b3ece6f1d0a1fd4aaf2f547f7c85.jpg" + }, + "sample": { + "has": true, + "height": 1062, + "width": 850, + "url": "https://static1.e621.net/data/sample/a6/b9/a6b9b3ece6f1d0a1fd4aaf2f547f7c85.jpg", + "alternates": {} + }, + "score": { + "up": 0, + "down": 0, + "total": 0 + }, + "tags": { + "general": [ + "ambiguous_gender", + "anal", + "anal_penetration", + "animal_genitalia", + "animal_penis", + "anthro", + "anus", + "asphyxiation", + "balls", + "bodily_fluids", + "butt", + "canine_genitalia", + "canine_penis", + "choking", + "cum", + "cum_in_ass", + "cum_inside", + "duo", + "forest", + "forest_background", + "genital_fluids", + "genitals", + "halloween_theme", + "hindpaw", + "intersex", + "intersex/male", + "male", + "male/male", + "nature", + "nature_background", + "pawpads", + "paws", + "penetration", + "penis", + "plant", + "tree" + ], + "artist": [ + "nehocane" + ], + "copyright": [], + "character": [ + "ych_(character)" + ], + "species": [ + "canid", + "canine", + "mammal", + "were", + "werecanid", + "werecanine", + "werewolf" + ], + "invalid": [], + "meta": [ + "absurd_res", + "digital_drawing_(artwork)", + "digital_media_(artwork)", + "digital_painting_(artwork)", + "hi_res" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283563, + "flags": { + "pending": true, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "e", + "fav_count": 0, + "sources": [], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 1211527, + "description": "Since Halloween is coming up I thought I’d give everyone something to drool over. There’s a male and female version of the ych it is $30 no restrictions and it’s up on my dealers den.\r\nhttps://www.thedealersden.com/listing/werewolf-forest-ych/248024", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371096, + "created_at": "2023-10-24T09:34:48.287-04:00", + "updated_at": "2023-10-24T09:40:23.907-04:00", + "file": { + "width": 1280, + "height": 1012, + "ext": "jpg", + "size": 370661, + "md5": "40ea035ac64c490c25280e00da2f3c78", + "url": "https://static1.e621.net/data/40/ea/40ea035ac64c490c25280e00da2f3c78.jpg" + }, + "preview": { + "width": 150, + "height": 118, + "url": "https://static1.e621.net/data/preview/40/ea/40ea035ac64c490c25280e00da2f3c78.jpg" + }, + "sample": { + "has": true, + "height": 672, + "width": 850, + "url": "https://static1.e621.net/data/sample/40/ea/40ea035ac64c490c25280e00da2f3c78.jpg", + "alternates": {} + }, + "score": { + "up": 2, + "down": 0, + "total": 2 + }, + "tags": { + "general": [ + "\u003c3", + "anthro", + "belly", + "belly_markings", + "bodily_fluids", + "crying", + "cub", + "eyebrows", + "female", + "frown", + "fur", + "handpaw", + "hindpaw", + "lyrics", + "markings", + "multicolored_body", + "multicolored_fur", + "pawpads", + "paws", + "pigtails", + "round_ears", + "sitting", + "solo", + "tail", + "tail_tuft", + "tears", + "text", + "tuft", + "young" + ], + "artist": [ + "pekopekokuma" + ], + "copyright": [ + "jack_stauber" + ], + "character": [], + "species": [ + "bear", + "felid", + "giant_panda", + "hybrid", + "lion", + "mammal", + "pantherine" + ], + "invalid": [], + "meta": [ + "english_text" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283556, + "flags": { + "pending": true, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "s", + "fav_count": 2, + "sources": [ + "https://www.furaffinity.net/view/46071891/", + "https://d.furaffinity.net/art/pekopekokuma/1645492501/1645492501.pekopekokuma_a195f045-c472-4a62-8553-f2a506f79fbf.jpg" + ], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 1472475, + "description": "", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371095, + "created_at": "2023-10-24T09:34:38.498-04:00", + "updated_at": "2023-10-24T09:35:13.646-04:00", + "file": { + "width": 1200, + "height": 1439, + "ext": "png", + "size": 1451700, + "md5": "05319f06e16faec238beb162e6f08e57", + "url": "https://static1.e621.net/data/05/31/05319f06e16faec238beb162e6f08e57.png" + }, + "preview": { + "width": 125, + "height": 150, + "url": "https://static1.e621.net/data/preview/05/31/05319f06e16faec238beb162e6f08e57.jpg" + }, + "sample": { + "has": true, + "height": 1019, + "width": 850, + "url": "https://static1.e621.net/data/sample/05/31/05319f06e16faec238beb162e6f08e57.jpg", + "alternates": {} + }, + "score": { + "up": 1, + "down": 0, + "total": 1 + }, + "tags": { + "general": [ + "animal_genitalia", + "animal_penis", + "anthro", + "bdsm", + "biped", + "blush", + "canine_genitalia", + "canine_penis", + "clothed", + "clothing", + "coat", + "collar", + "countershade_face", + "countershade_fur", + "countershading", + "duo", + "erection", + "eyebrows", + "fingers", + "fur", + "genitals", + "knot", + "leash", + "leash_pull", + "leashed_collar", + "male", + "male/male", + "one_eye_closed", + "open_mouth", + "open_smile", + "penis", + "raincoat", + "smile", + "stairs", + "tail", + "text", + "topwear", + "translucent_raincoat" + ], + "artist": [ + "kurulz" + ], + "copyright": [], + "character": [], + "species": [ + "canid", + "canine", + "canis", + "domestic_dog", + "mammal" + ], + "invalid": [], + "meta": [ + "english_text", + "hi_res" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283553, + "flags": { + "pending": false, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": true, + "deleted": false + }, + "rating": "e", + "fav_count": 1, + "sources": [ + "https://itaku.ee/images/644453", + "https://itaku.ee/api/media_2/gallery_imgs/rain-stop-miss-you-small_nsfw_vkbnUgy.png" + ], + "pools": [], + "relationships": { + "parent_id": 4371094, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 509791, + "description": "", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371094, + "created_at": "2023-10-24T09:34:27.724-04:00", + "updated_at": "2023-10-24T09:40:08.439-04:00", + "file": { + "width": 1200, + "height": 1439, + "ext": "png", + "size": 1272406, + "md5": "edc0e70af2a0a92f81a4030abec51650", + "url": "https://static1.e621.net/data/ed/c0/edc0e70af2a0a92f81a4030abec51650.png" + }, + "preview": { + "width": 125, + "height": 150, + "url": "https://static1.e621.net/data/preview/ed/c0/edc0e70af2a0a92f81a4030abec51650.jpg" + }, + "sample": { + "has": true, + "height": 1019, + "width": 850, + "url": "https://static1.e621.net/data/sample/ed/c0/edc0e70af2a0a92f81a4030abec51650.jpg", + "alternates": {} + }, + "score": { + "up": 0, + "down": 0, + "total": 0 + }, + "tags": { + "general": [ + "anthro", + "biped", + "clothed", + "clothing", + "coat", + "countershade_face", + "countershade_fur", + "countershading", + "duo", + "eyebrows", + "fingers", + "fur", + "male", + "open_mouth", + "open_smile", + "raincoat", + "red_umbrella", + "smile", + "stairs", + "tail", + "topwear", + "umbrella", + "yellow_raincoat" + ], + "artist": [ + "kurulz" + ], + "copyright": [], + "character": [], + "species": [ + "canid", + "canine", + "canis", + "domestic_dog", + "mammal" + ], + "invalid": [], + "meta": [ + "hi_res" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283554, + "flags": { + "pending": false, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": true, + "deleted": false + }, + "rating": "s", + "fav_count": 1, + "sources": [ + "https://itaku.ee/images/644454", + "https://itaku.ee/api/media_2/gallery_imgs/rain-stop-miss-you-small-nsfw_ADiJYIR.png" + ], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": true, + "has_active_children": true, + "children": [ + 4371095 + ] + }, + "approver_id": null, + "uploader_id": 509791, + "description": "", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371093, + "created_at": "2023-10-24T09:32:22.935-04:00", + "updated_at": "2023-10-24T09:32:22.935-04:00", + "file": { + "width": 3204, + "height": 1896, + "ext": "png", + "size": 1508206, + "md5": "2b4a9f0e97d998a98af2cb79a600cb36", + "url": "https://static1.e621.net/data/2b/4a/2b4a9f0e97d998a98af2cb79a600cb36.png" + }, + "preview": { + "width": 150, + "height": 88, + "url": "https://static1.e621.net/data/preview/2b/4a/2b4a9f0e97d998a98af2cb79a600cb36.jpg" + }, + "sample": { + "has": true, + "height": 502, + "width": 850, + "url": "https://static1.e621.net/data/sample/2b/4a/2b4a9f0e97d998a98af2cb79a600cb36.jpg", + "alternates": {} + }, + "score": { + "up": 1, + "down": 0, + "total": 1 + }, + "tags": { + "general": [ + "\u003c3", + "consentacles", + "female", + "feral", + "frill_(anatomy)", + "genitals", + "happy", + "horn", + "looking_pleasured", + "pussy", + "solo", + "solo_focus", + "tail", + "tentacles", + "wings" + ], + "artist": [ + "pandoraingrid_(artist)" + ], + "copyright": [ + "wings_of_fire" + ], + "character": [ + "glowworm_(foxtail74)" + ], + "species": [ + "dragon", + "nightwing_(wof)", + "rainwing_(wof)" + ], + "invalid": [], + "meta": [ + "absurd_res", + "hi_res" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283541, + "flags": { + "pending": true, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "e", + "fav_count": 0, + "sources": [ + "https://www.furaffinity.net/user/pandoraingrid/" + ], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 1667630, + "description": "Commission for FoxTail74 by PandoraIngrid", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371092, + "created_at": "2023-10-24T09:29:27.247-04:00", + "updated_at": "2023-10-24T09:35:29.730-04:00", + "file": { + "width": 1752, + "height": 3745, + "ext": "jpg", + "size": 472522, + "md5": "ec56842ea83133ce9f54007e7fec763a", + "url": "https://static1.e621.net/data/ec/56/ec56842ea83133ce9f54007e7fec763a.jpg" + }, + "preview": { + "width": 70, + "height": 150, + "url": "https://static1.e621.net/data/preview/ec/56/ec56842ea83133ce9f54007e7fec763a.jpg" + }, + "sample": { + "has": true, + "height": 1816, + "width": 850, + "url": "https://static1.e621.net/data/sample/ec/56/ec56842ea83133ce9f54007e7fec763a.jpg", + "alternates": {} + }, + "score": { + "up": 0, + "down": 0, + "total": 0 + }, + "tags": { + "general": [ + "4_arms", + "big_breasts", + "blush", + "blush_lines", + "bodily_fluids", + "breasts", + "dialogue", + "duo", + "eyelashes", + "featureless_breasts", + "featureless_crotch", + "female", + "front_view", + "hair", + "huge_breasts", + "male", + "mouthless", + "multi_arm", + "multi_eye", + "multi_limb", + "navel", + "pink_sclera", + "pseudo_hair", + "red_eyes", + "short_hair", + "small_waist", + "speech_bubble", + "sweat", + "sweatdrop", + "text" + ], + "artist": [ + "menma911" + ], + "copyright": [ + "armored_core_(series)" + ], + "character": [ + "621_(armored_core)", + "ayre_(armored_core)" + ], + "species": [ + "alien", + "alien_humanoid", + "eldritch_abomination", + "human", + "humanoid", + "mammal" + ], + "invalid": [], + "meta": [ + "absurd_res", + "hi_res", + "japanese_text", + "translated" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283560, + "flags": { + "pending": true, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "q", + "fav_count": 0, + "sources": [ + "https://twitter.com/menma911/status/1716185581464719385" + ], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 1006937, + "description": "621とエアちゃん", + "comment_count": 0, + "is_favorited": false, + "has_notes": true, + "duration": null + }, + { + "id": 4371091, + "created_at": "2023-10-24T09:28:30.560-04:00", + "updated_at": "2023-10-24T09:38:00.969-04:00", + "file": { + "width": 1895, + "height": 1944, + "ext": "png", + "size": 3398985, + "md5": "2a268af6823df64306af6ee2d974e06d", + "url": "https://static1.e621.net/data/2a/26/2a268af6823df64306af6ee2d974e06d.png" + }, + "preview": { + "width": 146, + "height": 150, + "url": "https://static1.e621.net/data/preview/2a/26/2a268af6823df64306af6ee2d974e06d.jpg" + }, + "sample": { + "has": true, + "height": 871, + "width": 850, + "url": "https://static1.e621.net/data/sample/2a/26/2a268af6823df64306af6ee2d974e06d.jpg", + "alternates": {} + }, + "score": { + "up": 1, + "down": 0, + "total": 1 + }, + "tags": { + "general": [ + "\u003c3", + "air_inflation", + "anthro", + "anus", + "anus_expansion", + "areola", + "aroused", + "belly", + "belly_expansion", + "belly_inflation", + "belly_squish", + "big_anus", + "big_areola", + "big_belly", + "big_breasts", + "big_butt", + "big_pussy", + "black_eyes", + "black_sclera", + "blue_eyes", + "blush", + "body_inflation", + "breast_expansion", + "breast_squish", + "breasts", + "building", + "butt", + "butt_expansion", + "buttplug", + "city", + "close-up", + "clothing", + "cloud", + "duo", + "embarrassed", + "expansion", + "face_in_pussy", + "female", + "floating", + "genital_expansion", + "genitals", + "huge_anus", + "huge_areola", + "huge_breasts", + "huge_butt", + "huge_cheeks", + "hyper", + "hyper_anus", + "hyper_areola", + "hyper_belly", + "hyper_breasts", + "hyper_butt", + "hyper_genitalia", + "hyper_inflation", + "hyper_nipples", + "hyper_pussy", + "immobile", + "inflation", + "inflation_fetish", + "nipples", + "open_mouth", + "parade", + "pink_areola", + "pink_nipples", + "plug_(sex_toy)", + "puffed_cheeks", + "puffy_nipples", + "purple_clothing", + "pussy", + "pussy_expansion", + "rear_view", + "sex_toy", + "sky", + "squish", + "string", + "stuck", + "tail", + "white_body", + "white_tail", + "yellow_body", + "yellow_tail" + ], + "artist": [ + "dragonfron" + ], + "copyright": [ + "bandai_namco", + "digimon", + "undertale_(series)" + ], + "character": [ + "toriel" + ], + "species": [ + "bovid", + "caprine", + "digimon_(species)", + "goat", + "mammal", + "renamon" + ], + "invalid": [], + "meta": [ + "2023", + "colored", + "digital_media_(artwork)", + "hi_res", + "shaded" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283564, + "flags": { + "pending": false, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "e", + "fav_count": 4, + "sources": [ + "https://www.furaffinity.net/view/54166974/", + "https://d.furaffinity.net/art/dragonfron/1698123231/1698123231.dragonfron_deviant1over2_comm.png" + ], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": 95927, + "uploader_id": 1444766, + "description": "Huge renamon and toriel parade balloon, unfortunately renaloon is too big to fit between tall building, while toriel get shoved to Rena's behind and Renamon really enjoying it ?!!", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371090, + "created_at": "2023-10-24T09:27:44.461-04:00", + "updated_at": "2023-10-24T09:36:36.213-04:00", + "file": { + "width": 3508, + "height": 2480, + "ext": "png", + "size": 3630165, + "md5": "29c5a1dccc458d30e0cd678176756dc1", + "url": "https://static1.e621.net/data/29/c5/29c5a1dccc458d30e0cd678176756dc1.png" + }, + "preview": { + "width": 150, + "height": 106, + "url": "https://static1.e621.net/data/preview/29/c5/29c5a1dccc458d30e0cd678176756dc1.jpg" + }, + "sample": { + "has": true, + "height": 600, + "width": 850, + "url": "https://static1.e621.net/data/sample/29/c5/29c5a1dccc458d30e0cd678176756dc1.jpg", + "alternates": {} + }, + "score": { + "up": 5, + "down": 0, + "total": 5 + }, + "tags": { + "general": [ + "blush", + "bodily_fluids", + "breasts", + "cum", + "doggystyle", + "duo", + "female", + "from_behind_position", + "genital_fluids", + "male", + "male/female", + "orgasm", + "sex", + "thick_thighs" + ], + "artist": [ + "goopyarts" + ], + "copyright": [], + "character": [], + "species": [ + "avian", + "kobold", + "scalie" + ], + "invalid": [], + "meta": [ + "absurd_res", + "hi_res" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283566, + "flags": { + "pending": false, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "e", + "fav_count": 5, + "sources": [ + "https://itaku.ee/images/644457", + "https://www.furaffinity.net/view/54169524/" + ], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": 95927, + "uploader_id": 367734, + "description": "Support me on Patreon for illustrations, and comic pages weeks in advance! \n\"https://www.patreon.com/ScalieArts\":https://www.patreon.com/ScalieArts\n\nComic and art pack store:\n\"https://nsfwscaliearts.gumroad.com\":https://nsfwscaliearts.gumroad.com/", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371089, + "created_at": "2023-10-24T09:26:02.783-04:00", + "updated_at": "2023-10-24T09:41:08.222-04:00", + "file": { + "width": 1515, + "height": 1368, + "ext": "png", + "size": 1703833, + "md5": "7c5c62f252b89d9e7f8181a20c480b52", + "url": null + }, + "preview": { + "width": 150, + "height": 135, + "url": null + }, + "sample": { + "has": true, + "height": 767, + "width": 850, + "url": null, + "alternates": {} + }, + "score": { + "up": 8, + "down": 0, + "total": 8 + }, + "tags": { + "general": [ + "abdominal_bulge", + "accessory", + "anal", + "anal_penetration", + "animal_genitalia", + "animal_penis", + "anus", + "anvil_position", + "arms_above_head", + "ass_up", + "audience", + "balls", + "bead_necklace", + "bestiality", + "big_balls", + "big_dom_small_sub", + "big_penis", + "black_clothing", + "black_eyebrows", + "black_nose", + "black_shirt", + "black_topwear", + "blep", + "blue_bottomwear", + "blue_bracelet", + "blue_clothing", + "blue_hair_tie", + "blue_jeans", + "blue_pants", + "blue_sky", + "blurred_background", + "bodily_fluids", + "bottomwear", + "bracelet", + "breasts", + "brown_balls", + "brown_body", + "brown_ears", + "brown_eyes", + "brown_fur", + "brown_hair", + "building", + "butt", + "camera_flash", + "canine_genitalia", + "canine_penis", + "child", + "city", + "clenched_teeth", + "clothed", + "clothed_sex", + "clothing", + "clothing_lift", + "clothing_pull", + "crowd", + "cuffed_jeans", + "dark_balls", + "denim", + "denim_clothing", + "dominant", + "dominant_feral", + "dominant_male", + "dripping", + "drooling", + "duo", + "ear_piercing", + "ear_ring", + "erect_nipples", + "erection", + "exhibitionism", + "eyebrows", + "eyes_closed", + "female", + "female_on_feral", + "female_penetrated", + "feral", + "feral_penetrating", + "feral_penetrating_female", + "feral_penetrating_human", + "flannel_shirt", + "floppy_ears", + "foot_on_head", + "footwear", + "from_front_position", + "fur", + "genital_fluids", + "genitals", + "glistening", + "glistening_bodily_fluids", + "glistening_eyes", + "glistening_genitalia", + "glistening_penis", + "glistening_saliva", + "gold_ear_ring", + "green_clothing", + "green_footwear", + "green_shoes", + "green_sneakers", + "grimace", + "group", + "hair", + "hair_accessory", + "hair_tie", + "hands_behind_head", + "human_on_feral", + "human_penetrated", + "interspecies", + "jeans", + "jewelry", + "knot", + "know", + "large_penetration", + "larger_feral", + "larger_male", + "legs_above_head", + "legs_up", + "loli", + "looking_away", + "looking_pleasured", + "male", + "male/female", + "male_on_human", + "male_penetrating", + "male_penetrating_female", + "male_penetrating_human", + "multicolored_clothing", + "multicolored_footwear", + "multicolored_shirt", + "multicolored_shoes", + "multicolored_topwear", + "nipples", + "on_ground", + "one_eye_closed", + "outside", + "pants", + "pattern_clothing", + "pattern_shirt", + "pattern_topwear", + "pavement", + "penetration", + "penis", + "piercing", + "pigtails", + "piledriver_position", + "pink_nipples", + "pinned", + "plaid", + "plaid_clothing", + "plaid_shirt", + "plaid_topwear", + "public", + "public_exposure", + "public_sex", + "questionable_consent", + "raised_leg", + "red_clothing", + "red_penis", + "red_shirt", + "red_topwear", + "restrained", + "ring_piercing", + "saliva", + "saliva_drip", + "saliva_on_face", + "saliva_on_tongue", + "sex", + "shirt", + "shirt_lift", + "shirt_pull", + "shirt_up", + "shoes", + "short_tail", + "shoulder_stand", + "size_difference", + "sky", + "skyscraper", + "small_breasts", + "smaller_female", + "smaller_human", + "spread_legs", + "spreading", + "sweat", + "sweaty_back", + "sweaty_face", + "tail", + "tan_body", + "tan_fur", + "teeth", + "tongue", + "tongue_out", + "topwear", + "topwear_pull", + "torn_bottomwear", + "torn_clothing", + "torn_jeans", + "torn_pants", + "two_tone_clothing", + "two_tone_footwear", + "two_tone_shirt", + "two_tone_shoes", + "two_tone_sneakers", + "two_tone_topwear", + "underwear", + "vein", + "veiny_knot", + "veiny_penis", + "white_body", + "white_clothing", + "white_footwear", + "white_fur", + "white_shoes", + "white_sneakers", + "yellow_clothing", + "yellow_shirt", + "yellow_topwear", + "young" + ], + "artist": [ + "mono_(artist)" + ], + "copyright": [ + "100_percent_wolf" + ], + "character": [ + "batty_(100_percent_wolf)", + "the_doog_(100_percent_wolf)" + ], + "species": [ + "canid", + "canine", + "canis", + "domestic_dog", + "human", + "mammal", + "mastiff", + "molosser" + ], + "invalid": [], + "meta": [ + "hi_res" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283531, + "flags": { + "pending": true, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "e", + "fav_count": 15, + "sources": [ + "https://baraag.net/@Mon0/110905221200432664", + "https://media.baraag.net/media_attachments/files/110/905/214/788/868/083/original/73631db7c378d477.png" + ], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": true, + "has_active_children": true, + "children": [ + 4265189 + ] + }, + "approver_id": null, + "uploader_id": 747940, + "description": "", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371088, + "created_at": "2023-10-24T09:25:46.292-04:00", + "updated_at": "2023-10-24T09:40:12.471-04:00", + "file": { + "width": 2500, + "height": 2400, + "ext": "png", + "size": 4106776, + "md5": "9b68486d299053a95d73921baa20fc9a", + "url": "https://static1.e621.net/data/9b/68/9b68486d299053a95d73921baa20fc9a.png" + }, + "preview": { + "width": 150, + "height": 144, + "url": "https://static1.e621.net/data/preview/9b/68/9b68486d299053a95d73921baa20fc9a.jpg" + }, + "sample": { + "has": true, + "height": 816, + "width": 850, + "url": "https://static1.e621.net/data/sample/9b/68/9b68486d299053a95d73921baa20fc9a.jpg", + "alternates": {} + }, + "score": { + "up": 11, + "down": 0, + "total": 11 + }, + "tags": { + "general": [ + "after_anal", + "after_sex", + "animal_genitalia", + "animal_penis", + "anus", + "balls", + "blush", + "bodily_fluids", + "brown_body", + "brown_fur", + "canine_genitalia", + "canine_penis", + "cum", + "cum_in_ass", + "cum_inside", + "cum_on_penis", + "cum_pool", + "curled_tail", + "duo", + "feral", + "feral_on_feral", + "fur", + "gaping", + "gaping_anus", + "genital_fluids", + "genitals", + "knot", + "male", + "male/male", + "orange_body", + "orange_fur", + "penis", + "raised_leg", + "tail", + "tongue" + ], + "artist": [ + "hdoge_(artist)" + ], + "copyright": [], + "character": [ + "haku_(hdoge)", + "whiskey_(hdoge)" + ], + "species": [ + "canid", + "canine", + "canis", + "domestic_dog", + "mammal", + "shiba_inu", + "spitz" + ], + "invalid": [], + "meta": [ + "absurd_res", + "hi_res" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283521, + "flags": { + "pending": true, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "e", + "fav_count": 12, + "sources": [], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 1099488, + "description": "Doge buns: cream filled above max capacity.", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371087, + "created_at": "2023-10-24T09:23:38.690-04:00", + "updated_at": "2023-10-24T09:24:12.476-04:00", + "file": { + "width": 1416, + "height": 1731, + "ext": "jpg", + "size": 460038, + "md5": "7c730bd9838c22005035bb2811fa365d", + "url": "https://static1.e621.net/data/7c/73/7c730bd9838c22005035bb2811fa365d.jpg" + }, + "preview": { + "width": 122, + "height": 150, + "url": "https://static1.e621.net/data/preview/7c/73/7c730bd9838c22005035bb2811fa365d.jpg" + }, + "sample": { + "has": true, + "height": 1039, + "width": 850, + "url": "https://static1.e621.net/data/sample/7c/73/7c730bd9838c22005035bb2811fa365d.jpg", + "alternates": {} + }, + "score": { + "up": 1, + "down": 0, + "total": 1 + }, + "tags": { + "general": [ + "1_eye", + "blonde_hair", + "blue_nails", + "bodily_fluids", + "bone", + "breasts", + "brown_eyes", + "clothing", + "colored_nails", + "crying", + "eyelashes", + "feathered_wings", + "feathers", + "female", + "food", + "hair", + "halo", + "holding_mirror", + "holding_object", + "meat", + "mirror", + "mouthless", + "nails", + "noseless", + "not_furry", + "pink_body", + "pink_skin", + "pork", + "shirt", + "small_breasts", + "solo", + "tank_top", + "tears", + "topwear", + "wide_eyed", + "wings" + ], + "artist": [ + "thatguywithabadattitude" + ], + "copyright": [ + "jack_stauber" + ], + "character": [ + "hamantha_(jack_stauber)" + ], + "species": [ + "angel", + "humanoid", + "meat_creature" + ], + "invalid": [], + "meta": [ + "hi_res" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283515, + "flags": { + "pending": true, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "s", + "fav_count": 0, + "sources": [ + "https://www.tumblr.com/thatguywithabadattitude/731374109620092929/flowing-hair-she-was-a-cutie-pie-meaty-face-and?source=share\u0026ref=_tumblr", + "https://64.media.tumblr.com/855f391912efa37a94630419dfed560b/670d8d959a8af131-af/s2048x3072/9b6e402b556bd9e917705362851d3a7b18df5fe7.pnj" + ], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 1472475, + "description": "", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371086, + "created_at": "2023-10-24T09:22:21.333-04:00", + "updated_at": "2023-10-24T09:22:35.586-04:00", + "file": { + "width": 3308, + "height": 3508, + "ext": "png", + "size": 3799956, + "md5": "0f056297851ab7f54c988a0305c43526", + "url": "https://static1.e621.net/data/0f/05/0f056297851ab7f54c988a0305c43526.png" + }, + "preview": { + "width": 141, + "height": 150, + "url": "https://static1.e621.net/data/preview/0f/05/0f056297851ab7f54c988a0305c43526.jpg" + }, + "sample": { + "has": true, + "height": 901, + "width": 850, + "url": "https://static1.e621.net/data/sample/0f/05/0f056297851ab7f54c988a0305c43526.jpg", + "alternates": {} + }, + "score": { + "up": 0, + "down": 0, + "total": 0 + }, + "tags": { + "general": [ + "4_fingers", + "big_ears", + "big_glasses", + "brown_clothing", + "brown_gloves", + "brown_handwear", + "clothed", + "clothing", + "dialogue", + "ear_piercing", + "eyewear", + "fake_horns", + "female", + "fingerless_gloves", + "fingers", + "glasses", + "gloves", + "green_body", + "green_eyes", + "green_hair", + "green_skin", + "grey_background", + "hair", + "handwear", + "open_mouth", + "piercing", + "round_glasses", + "scroll", + "short_hair", + "simple_background", + "solo", + "tail", + "tail_tuft", + "tentacles", + "text", + "tuft" + ], + "artist": [ + "critterstew" + ], + "copyright": [], + "character": [], + "species": [ + "goblin", + "humanoid" + ], + "invalid": [], + "meta": [ + "absurd_res", + "hi_res" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283501, + "flags": { + "pending": true, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "s", + "fav_count": 0, + "sources": [ + "https://www.newgrounds.com/art/view/critterstew/gobtober-day-10-summoner" + ], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 1006937, + "description": "Gobtober day 10 - Summoner!", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371085, + "created_at": "2023-10-24T09:21:43.225-04:00", + "updated_at": "2023-10-24T09:37:07.426-04:00", + "file": { + "width": 848, + "height": 1200, + "ext": "jpg", + "size": 192171, + "md5": "e347e66d73ca1b6103def254fad71fb4", + "url": "https://static1.e621.net/data/e3/47/e347e66d73ca1b6103def254fad71fb4.jpg" + }, + "preview": { + "width": 106, + "height": 150, + "url": "https://static1.e621.net/data/preview/e3/47/e347e66d73ca1b6103def254fad71fb4.jpg" + }, + "sample": { + "has": false, + "height": 1200, + "width": 848, + "url": "https://static1.e621.net/data/e3/47/e347e66d73ca1b6103def254fad71fb4.jpg", + "alternates": {} + }, + "score": { + "up": 4, + "down": 0, + "total": 4 + }, + "tags": { + "general": [ + "3_toes", + "4_arms", + "4_fingers", + "anthro", + "barefoot", + "black_nose", + "blue_hair", + "bottomwear", + "candy", + "claws", + "clothed", + "clothing", + "cotton_candy", + "dessert", + "duo", + "faceless_character", + "faceless_male", + "feet", + "female", + "finger_claws", + "fingers", + "food", + "fur", + "grey_body", + "grey_fur", + "grin", + "hair", + "hindpaw", + "humanoid_hands", + "insect_wings", + "kneeling", + "male", + "male/female", + "multi_arm", + "multi_limb", + "multicolored_body", + "multicolored_fur", + "multicolored_hair", + "pants", + "paws", + "pink_hair", + "shirt", + "shorts", + "smile", + "standing", + "tank_top", + "teeth", + "toe_claws", + "toes", + "topwear", + "torn_bottomwear", + "torn_clothing", + "torn_pants", + "torn_tank_top", + "two_tone_body", + "two_tone_fur", + "two_tone_hair", + "white_body", + "white_fur", + "wings", + "yellow_body", + "yellow_fur" + ], + "artist": [ + "harubaki" + ], + "copyright": [ + "helluva_boss" + ], + "character": [ + "queen_bee-lzebub_(helluva_boss)", + "vortex_(helluva_boss)" + ], + "species": [ + "arthropod", + "bee", + "canid", + "canid_demon", + "canine", + "demon", + "fox", + "hellhound", + "hybrid", + "hymenopteran", + "insect", + "mammal" + ], + "invalid": [], + "meta": [ + "2023", + "hi_res" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283499, + "flags": { + "pending": false, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "s", + "fav_count": 5, + "sources": [ + "https://www.furaffinity.net/view/54165349/", + "https://twitter.com/HarubakiArtist/status/1713641894444503411", + "https://pbs.twimg.com/media/F8fuAwAXUAArRcv?format=jpg\u0026name=orig" + ], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 461163, + "description": "", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371084, + "created_at": "2023-10-24T09:21:29.286-04:00", + "updated_at": "2023-10-24T09:32:36.188-04:00", + "file": { + "width": 2048, + "height": 1440, + "ext": "jpg", + "size": 330009, + "md5": "7aa173edebb069e70a0d328d2c9ec3cf", + "url": "https://static1.e621.net/data/7a/a1/7aa173edebb069e70a0d328d2c9ec3cf.jpg" + }, + "preview": { + "width": 150, + "height": 105, + "url": "https://static1.e621.net/data/preview/7a/a1/7aa173edebb069e70a0d328d2c9ec3cf.jpg" + }, + "sample": { + "has": true, + "height": 597, + "width": 850, + "url": "https://static1.e621.net/data/sample/7a/a1/7aa173edebb069e70a0d328d2c9ec3cf.jpg", + "alternates": {} + }, + "score": { + "up": 1, + "down": 0, + "total": 1 + }, + "tags": { + "general": [ + "anthro", + "ear_piercing", + "knife", + "male", + "muscular", + "muscular_anthro", + "muscular_male", + "nipple_piercing", + "nipples", + "nude", + "pecs", + "piercing", + "red_eyes", + "scar", + "solo" + ], + "artist": [ + "zark" + ], + "copyright": [], + "character": [], + "species": [ + "canid", + "canine", + "canis", + "mammal", + "were", + "werecanid", + "werecanine", + "werewolf", + "wolf" + ], + "invalid": [], + "meta": [ + "half-length_portrait", + "hi_res", + "line_art", + "portrait", + "sketch" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283503, + "flags": { + "pending": true, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "q", + "fav_count": 2, + "sources": [ + "https://twitter.com/Zark_ID/status/1601391260392370176" + ], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 341960, + "description": "", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371083, + "created_at": "2023-10-24T09:20:44.905-04:00", + "updated_at": "2023-10-24T09:40:01.300-04:00", + "file": { + "width": 1844, + "height": 2500, + "ext": "png", + "size": 3791477, + "md5": "455c88707f9c28cd4cc21ac0c8b7ba29", + "url": "https://static1.e621.net/data/45/5c/455c88707f9c28cd4cc21ac0c8b7ba29.png" + }, + "preview": { + "width": 110, + "height": 150, + "url": "https://static1.e621.net/data/preview/45/5c/455c88707f9c28cd4cc21ac0c8b7ba29.jpg" + }, + "sample": { + "has": true, + "height": 1152, + "width": 850, + "url": "https://static1.e621.net/data/sample/45/5c/455c88707f9c28cd4cc21ac0c8b7ba29.jpg", + "alternates": {} + }, + "score": { + "up": 3, + "down": 0, + "total": 3 + }, + "tags": { + "general": [ + "\u003c3", + "anus", + "areola", + "black_areola", + "black_body", + "blush", + "bodily_fluids", + "breasts", + "butt", + "cape", + "clothing", + "fecharis", + "female", + "genitals", + "looking_back", + "melee_weapon", + "navel", + "nipples", + "non-mammal_breasts", + "pussy", + "question_mark", + "solo", + "sweat", + "sword", + "text", + "torn_clothing", + "wardrobe_malfunction", + "weapon" + ], + "artist": [ + "azura_inalis" + ], + "copyright": [ + "hollow_knight", + "team_cherry" + ], + "character": [ + "hornet_(hollow_knight)" + ], + "species": [ + "arthropod" + ], + "invalid": [], + "meta": [ + "2023", + "absurd_res", + "english_text", + "hi_res" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283494, + "flags": { + "pending": true, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "e", + "fav_count": 8, + "sources": [ + "https://twitter.com/AzuraJayfox/status/1716804903065289080" + ], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 212270, + "description": "", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371082, + "created_at": "2023-10-24T09:20:03.580-04:00", + "updated_at": "2023-10-24T09:40:54.385-04:00", + "file": { + "width": 1400, + "height": 1700, + "ext": "png", + "size": 1797117, + "md5": "fb9df895e03c309b7213e6d5e86c77cc", + "url": "https://static1.e621.net/data/fb/9d/fb9df895e03c309b7213e6d5e86c77cc.png" + }, + "preview": { + "width": 123, + "height": 150, + "url": "https://static1.e621.net/data/preview/fb/9d/fb9df895e03c309b7213e6d5e86c77cc.jpg" + }, + "sample": { + "has": true, + "height": 1032, + "width": 850, + "url": "https://static1.e621.net/data/sample/fb/9d/fb9df895e03c309b7213e6d5e86c77cc.jpg", + "alternates": {} + }, + "score": { + "up": 0, + "down": -1, + "total": -1 + }, + "tags": { + "general": [ + "accessory", + "anthro", + "beak", + "big_breasts", + "blue_eyes", + "bow_ribbon", + "breasts", + "eating", + "eating_food", + "female", + "food", + "hair_accessory", + "hair_bow", + "hair_ribbon", + "holding_object", + "huge_breasts", + "machine", + "non-mammal_breasts", + "obese", + "obese_anthro", + "obese_female", + "overweight", + "overweight_anthro", + "overweight_female", + "pizza", + "pizza_box", + "ribbons", + "solo" + ], + "artist": [ + "plantedpot" + ], + "copyright": [ + "five_nights_at_freddy's", + "five_nights_at_freddy's:_security_breach", + "scottgames", + "steel_wool_studios" + ], + "character": [ + "glamrock_chica_(fnaf)" + ], + "species": [ + "animatronic", + "avian", + "bird", + "chicken", + "galliform", + "gallus_(genus)", + "phasianid", + "robot" + ], + "invalid": [], + "meta": [ + "hi_res" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283492, + "flags": { + "pending": true, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "q", + "fav_count": 4, + "sources": [ + "https://itaku.ee/images/628313" + ], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 332777, + "description": "h2.Pizza Compactor Chica\r\nA model upgrade for Chica made her a more capable way of turning leftover pizza into energy...the more she eats the more she moves - just try not to let your urges get the better of you and actively start feeding her...", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371081, + "created_at": "2023-10-24T09:16:35.633-04:00", + "updated_at": "2023-10-24T09:23:28.122-04:00", + "file": { + "width": 1938, + "height": 2600, + "ext": "png", + "size": 2403686, + "md5": "8fc95720d8c9be88076337ce7b79dc5d", + "url": "https://static1.e621.net/data/8f/c9/8fc95720d8c9be88076337ce7b79dc5d.png" + }, + "preview": { + "width": 111, + "height": 150, + "url": "https://static1.e621.net/data/preview/8f/c9/8fc95720d8c9be88076337ce7b79dc5d.jpg" + }, + "sample": { + "has": true, + "height": 1140, + "width": 850, + "url": "https://static1.e621.net/data/sample/8f/c9/8fc95720d8c9be88076337ce7b79dc5d.jpg", + "alternates": {} + }, + "score": { + "up": 2, + "down": -2, + "total": 0 + }, + "tags": { + "general": [ + "5_fingers", + "abs", + "alcohol", + "animal_genitalia", + "animal_penis", + "anthro", + "anthro_on_anthro", + "areola", + "balls", + "beverage", + "biceps", + "big_balls", + "big_breasts", + "big_penis", + "biped", + "blush", + "bodily_fluids", + "breasts", + "cocktail", + "duo", + "equine_genitalia", + "equine_penis", + "eyebrows", + "female", + "fingers", + "flared_penis", + "floppy_ears", + "genitals", + "half-erect", + "holding_beverage", + "holding_object", + "huge_balls", + "huge_breasts", + "huge_penis", + "interspecies", + "lactating", + "male", + "male/female", + "muscular", + "muscular_anthro", + "muscular_male", + "nipples", + "pecs", + "penis", + "pregnant", + "pregnant_female", + "tail", + "thick_thighs", + "vein", + "veiny_balls", + "veiny_penis" + ], + "artist": [ + "amartolocheri" + ], + "copyright": [], + "character": [], + "species": [ + "canid", + "canine", + "canis", + "domestic_dog", + "equid", + "equine", + "horse", + "mammal" + ], + "invalid": [], + "meta": [ + "absurd_res", + "hi_res", + "monochrome" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283482, + "flags": { + "pending": false, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": true, + "deleted": false + }, + "rating": "e", + "fav_count": 2, + "sources": [ + "https://itaku.ee/images/644289", + "https://itaku.ee/api/media_2/gallery_imgs/SHOTS-Sophie-Beach_Couple_2_j8JMSZ6.png" + ], + "pools": [], + "relationships": { + "parent_id": 4371080, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 509791, + "description": "", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371080, + "created_at": "2023-10-24T09:16:22.752-04:00", + "updated_at": "2023-10-24T09:16:55.812-04:00", + "file": { + "width": 1938, + "height": 2600, + "ext": "png", + "size": 2058501, + "md5": "4170add5b0dc814a25d15d1c5e46d9e0", + "url": "https://static1.e621.net/data/41/70/4170add5b0dc814a25d15d1c5e46d9e0.png" + }, + "preview": { + "width": 111, + "height": 150, + "url": "https://static1.e621.net/data/preview/41/70/4170add5b0dc814a25d15d1c5e46d9e0.jpg" + }, + "sample": { + "has": true, + "height": 1140, + "width": 850, + "url": "https://static1.e621.net/data/sample/41/70/4170add5b0dc814a25d15d1c5e46d9e0.jpg", + "alternates": {} + }, + "score": { + "up": 0, + "down": -1, + "total": -1 + }, + "tags": { + "general": [ + "5_fingers", + "abs", + "alcohol", + "anthro", + "anthro_on_anthro", + "areola", + "balls_outline", + "beverage", + "biceps", + "big_breasts", + "big_bulge", + "bikini", + "biped", + "breasts", + "bulge", + "clothed", + "clothing", + "cocktail", + "duo", + "eyebrows", + "female", + "fingers", + "floppy_ears", + "genital_outline", + "holding_beverage", + "holding_object", + "huge_breasts", + "huge_bulge", + "interspecies", + "male", + "male/female", + "muscular", + "muscular_anthro", + "muscular_male", + "pecs", + "penis_outline", + "swimwear", + "tail", + "thick_thighs" + ], + "artist": [ + "amartolocheri" + ], + "copyright": [], + "character": [], + "species": [ + "canid", + "canine", + "canis", + "domestic_dog", + "equid", + "equine", + "horse", + "mammal" + ], + "invalid": [], + "meta": [ + "absurd_res", + "hi_res", + "monochrome" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283483, + "flags": { + "pending": false, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": true, + "deleted": false + }, + "rating": "e", + "fav_count": 1, + "sources": [ + "https://itaku.ee/images/644287", + "https://itaku.ee/api/media_2/gallery_imgs/SHOTS-Sophie-Beach_Couple_1_DVNCGH2.png" + ], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": true, + "has_active_children": true, + "children": [ + 4371081 + ] + }, + "approver_id": null, + "uploader_id": 509791, + "description": "", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371079, + "created_at": "2023-10-24T09:14:53.640-04:00", + "updated_at": "2023-10-24T09:14:53.640-04:00", + "file": { + "width": 3171, + "height": 2159, + "ext": "png", + "size": 5232844, + "md5": "e3cd2adbe7d6395120aafafe615eb538", + "url": "https://static1.e621.net/data/e3/cd/e3cd2adbe7d6395120aafafe615eb538.png" + }, + "preview": { + "width": 150, + "height": 102, + "url": "https://static1.e621.net/data/preview/e3/cd/e3cd2adbe7d6395120aafafe615eb538.jpg" + }, + "sample": { + "has": true, + "height": 578, + "width": 850, + "url": "https://static1.e621.net/data/sample/e3/cd/e3cd2adbe7d6395120aafafe615eb538.jpg", + "alternates": {} + }, + "score": { + "up": 0, + "down": 0, + "total": 0 + }, + "tags": { + "general": [ + "anthro", + "artist_rayminonfox", + "bite", + "bite_mark", + "blood", + "bodily_fluids", + "duo", + "male", + "male/male", + "nibbling", + "teeth" + ], + "artist": [], + "copyright": [ + "baldur's_gate_3" + ], + "character": [ + "ame", + "ameko", + "astarion_(baldur's_gate)" + ], + "species": [ + "canid", + "canine", + "canis", + "mammal", + "vampire", + "wolf" + ], + "invalid": [], + "meta": [ + "hi_res" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283475, + "flags": { + "pending": true, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "q", + "fav_count": 0, + "sources": [ + "https://www.furaffinity.net/user/rayminonfox" + ], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 1322129, + "description": "Awesome Commission i got from Rayminonfox please check out their work on furaffinity", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371078, + "created_at": "2023-10-24T09:14:40.998-04:00", + "updated_at": "2023-10-24T09:37:43.175-04:00", + "file": { + "width": 2255, + "height": 2600, + "ext": "png", + "size": 3715279, + "md5": "d410dfa5f4a406202ca99197bac372a8", + "url": "https://static1.e621.net/data/d4/10/d410dfa5f4a406202ca99197bac372a8.png" + }, + "preview": { + "width": 130, + "height": 150, + "url": "https://static1.e621.net/data/preview/d4/10/d410dfa5f4a406202ca99197bac372a8.jpg" + }, + "sample": { + "has": true, + "height": 980, + "width": 850, + "url": "https://static1.e621.net/data/sample/d4/10/d410dfa5f4a406202ca99197bac372a8.jpg", + "alternates": {} + }, + "score": { + "up": 2, + "down": 0, + "total": 2 + }, + "tags": { + "general": [ + "4_arms", + "5_fingers", + "anal", + "anal_penetration", + "animal_genitalia", + "animal_penis", + "anthro", + "areola", + "bdsm", + "bedroom_sex", + "big_penis", + "black_body", + "black_fur", + "black_nails", + "black_nose", + "blind_eye", + "blush", + "bodily_fluids", + "breasts", + "butt", + "butt_grab", + "canine_genitalia", + "canine_penis", + "clothing", + "collar", + "colored_nails", + "dialogue", + "dildo", + "dildo_in_ass", + "dildo_insertion", + "dominant", + "dominant_anthro", + "dominant_female", + "dominatrix", + "duo", + "erect_nipples", + "erection", + "eyebrows", + "eyelashes", + "eyeshadow", + "female", + "fingers", + "fluffy", + "fluffy_chest", + "fluffy_ears", + "fluffy_tail", + "fur", + "genital_fluids", + "genitals", + "green_background", + "green_hair", + "grey_body", + "grey_eyes", + "grey_fur", + "grey_tail", + "hair", + "half-closed_eyes", + "hand_on_butt", + "inner_ear_fluff", + "insect_wings", + "jewelry", + "looking_at_another", + "looking_at_partner", + "makeup", + "male", + "male/female", + "male_penetrated", + "multi_arm", + "multi_limb", + "multicolored_body", + "multicolored_fur", + "multicolored_hair", + "multicolored_tail", + "nails", + "narrowed_eyes", + "navel", + "nipple_dip", + "nipples", + "nude", + "nude_anthro", + "nude_female", + "object_in_ass", + "open_mouth", + "pegging", + "penetration", + "penis", + "pink_areola", + "pink_eyes", + "pink_hair", + "pink_nipples", + "precum", + "presenting", + "presenting_penis", + "red_penis", + "red_sclera", + "scar", + "sex", + "sex_toy", + "sex_toy_in_ass", + "sex_toy_insertion", + "sex_toy_penetration", + "short_hair", + "simple_background", + "smile", + "smiling_at_partner", + "tail", + "teeth", + "text", + "toying_partner", + "translucent", + "translucent_hair", + "translucent_wings", + "tuft", + "two_tone_body", + "two_tone_fur", + "two_tone_tail", + "whip", + "whip_mark", + "white_body", + "white_fur", + "wings", + "yellow_body" + ], + "artist": [ + "malkytea" + ], + "copyright": [ + "helluva_boss" + ], + "character": [ + "queen_bee-lzebub_(helluva_boss)", + "vortex_(helluva_boss)" + ], + "species": [ + "arthropod", + "bee", + "canid", + "canid_demon", + "canine", + "canis", + "demon", + "hellhound", + "hymenopteran", + "insect", + "mammal", + "wolf" + ], + "invalid": [], + "meta": [ + "absurd_res", + "digital_media_(artwork)", + "english_text", + "hi_res" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283577, + "flags": { + "pending": false, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "e", + "fav_count": 10, + "sources": [ + "https://www.furaffinity.net/view/54163059", + "https://www.furaffinity.net/user/malkytea" + ], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": 95927, + "uploader_id": 1475528, + "description": "Flat colored pair for zoojames512", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371077, + "created_at": "2023-10-24T09:14:37.684-04:00", + "updated_at": "2023-10-24T09:37:40.715-04:00", + "file": { + "width": 1300, + "height": 1181, + "ext": "jpg", + "size": 110448, + "md5": "b0f2fef3e3b531387dbe6f0e15123fec", + "url": "https://static1.e621.net/data/b0/f2/b0f2fef3e3b531387dbe6f0e15123fec.jpg" + }, + "preview": { + "width": 150, + "height": 136, + "url": "https://static1.e621.net/data/preview/b0/f2/b0f2fef3e3b531387dbe6f0e15123fec.jpg" + }, + "sample": { + "has": true, + "height": 772, + "width": 850, + "url": "https://static1.e621.net/data/sample/b0/f2/b0f2fef3e3b531387dbe6f0e15123fec.jpg", + "alternates": {} + }, + "score": { + "up": 0, + "down": 0, + "total": 0 + }, + "tags": { + "general": [ + "anthro", + "blush", + "bra", + "chubby_female", + "clothing", + "necktie", + "panties", + "spread_legs", + "spreading", + "underwear", + "undressed" + ], + "artist": [ + "alpi" + ], + "copyright": [ + "undertale_(series)" + ], + "character": [ + "alphys" + ], + "species": [ + "dinosaur", + "reptile", + "scalie" + ], + "invalid": [], + "meta": [], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283575, + "flags": { + "pending": true, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "q", + "fav_count": 3, + "sources": [ + "https://x.com/ARUPUT_UT/status/1716475329571938440?s=20" + ], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 750607, + "description": "", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371076, + "created_at": "2023-10-24T09:14:30.100-04:00", + "updated_at": "2023-10-24T09:39:10.620-04:00", + "file": { + "width": 535, + "height": 750, + "ext": "gif", + "size": 18906220, + "md5": "b985e467efd0a8af51ed55f708d0b885", + "url": "https://static1.e621.net/data/b9/85/b985e467efd0a8af51ed55f708d0b885.gif" + }, + "preview": { + "width": 107, + "height": 150, + "url": "https://static1.e621.net/data/preview/b9/85/b985e467efd0a8af51ed55f708d0b885.jpg" + }, + "sample": { + "has": false, + "height": 750, + "width": 535, + "url": "https://static1.e621.net/data/b9/85/b985e467efd0a8af51ed55f708d0b885.gif", + "alternates": {} + }, + "score": { + "up": 20, + "down": -1, + "total": 19 + }, + "tags": { + "general": [ + "female", + "male", + "male/female" + ], + "artist": [ + "dagasi" + ], + "copyright": [ + "genshin_impact", + "mihoyo" + ], + "character": [ + "elphane_(genshin_impact)" + ], + "species": [ + "human", + "mammal", + "melusine_(genshin_impact)" + ], + "invalid": [], + "meta": [ + "animated" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283472, + "flags": { + "pending": true, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "e", + "fav_count": 51, + "sources": [ + "https://www.pixiv.net/artworks/112610947" + ], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 205980, + "description": "", + "comment_count": 1, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371075, + "created_at": "2023-10-24T09:12:41.164-04:00", + "updated_at": "2023-10-24T09:28:27.340-04:00", + "file": { + "width": 1200, + "height": 1565, + "ext": "jpg", + "size": 103685, + "md5": "4f4dd7723ac086a239b7f811e109bfe4", + "url": "https://static1.e621.net/data/4f/4d/4f4dd7723ac086a239b7f811e109bfe4.jpg" + }, + "preview": { + "width": 115, + "height": 150, + "url": "https://static1.e621.net/data/preview/4f/4d/4f4dd7723ac086a239b7f811e109bfe4.jpg" + }, + "sample": { + "has": true, + "height": 1108, + "width": 850, + "url": "https://static1.e621.net/data/sample/4f/4d/4f4dd7723ac086a239b7f811e109bfe4.jpg", + "alternates": {} + }, + "score": { + "up": 1, + "down": 0, + "total": 1 + }, + "tags": { + "general": [ + "anthro", + "clothing", + "female", + "nipples", + "outline", + "panties", + "sleeveless_shirt", + "solo", + "underwear" + ], + "artist": [ + "alpi" + ], + "copyright": [ + "undertale_(series)" + ], + "character": [ + "alphys" + ], + "species": [ + "dinosaur", + "reptile", + "scalie" + ], + "invalid": [], + "meta": [ + "hi_res" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283528, + "flags": { + "pending": true, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "q", + "fav_count": 4, + "sources": [ + "https://twitter.com/ARUPUT_UT/status/1715024426801254811" + ], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 750607, + "description": "", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371074, + "created_at": "2023-10-24T09:12:33.351-04:00", + "updated_at": "2023-10-24T09:36:54.623-04:00", + "file": { + "width": 2414, + "height": 4258, + "ext": "png", + "size": 2318989, + "md5": "dcf34aa4834ccb057070c9ffd1322508", + "url": "https://static1.e621.net/data/dc/f3/dcf34aa4834ccb057070c9ffd1322508.png" + }, + "preview": { + "width": 85, + "height": 150, + "url": "https://static1.e621.net/data/preview/dc/f3/dcf34aa4834ccb057070c9ffd1322508.jpg" + }, + "sample": { + "has": true, + "height": 1499, + "width": 850, + "url": "https://static1.e621.net/data/sample/dc/f3/dcf34aa4834ccb057070c9ffd1322508.jpg", + "alternates": {} + }, + "score": { + "up": 1, + "down": 0, + "total": 1 + }, + "tags": { + "general": [ + "armor", + "big_ears", + "blue_bottomwear", + "blue_clothing", + "bottomwear", + "breasts", + "brown_hair", + "cleavage", + "clothed", + "clothing", + "eyelashes", + "female", + "footwear", + "green_body", + "green_skin", + "grey_background", + "grey_clothing", + "grey_topwear", + "guard", + "hair", + "headgear", + "helmet", + "holding_object", + "holding_spear", + "holding_weapon", + "humanoid_pointy_ears", + "looking_at_viewer", + "low-angle_view", + "melee_weapon", + "navel", + "open_mouth", + "polearm", + "raised_leg", + "shoes", + "simple_background", + "solo", + "spear", + "text", + "thick_thighs", + "topwear", + "weapon", + "wide_hips", + "worm's-eye_view", + "yellow_eyes" + ], + "artist": [ + "critterstew" + ], + "copyright": [], + "character": [], + "species": [ + "goblin", + "humanoid" + ], + "invalid": [], + "meta": [ + "absurd_res", + "hi_res" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283468, + "flags": { + "pending": true, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "s", + "fav_count": 1, + "sources": [ + "https://www.newgrounds.com/art/view/critterstew/gobtober-day-9-guard" + ], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 1006937, + "description": "Gobtober day 9 - Guard", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371073, + "created_at": "2023-10-24T09:11:01.594-04:00", + "updated_at": "2023-10-24T09:28:30.347-04:00", + "file": { + "width": 1300, + "height": 1062, + "ext": "jpg", + "size": 99189, + "md5": "7966e5c0cf2c865c17d088d2e215a8f4", + "url": "https://static1.e621.net/data/79/66/7966e5c0cf2c865c17d088d2e215a8f4.jpg" + }, + "preview": { + "width": 150, + "height": 122, + "url": "https://static1.e621.net/data/preview/79/66/7966e5c0cf2c865c17d088d2e215a8f4.jpg" + }, + "sample": { + "has": true, + "height": 694, + "width": 850, + "url": "https://static1.e621.net/data/sample/79/66/7966e5c0cf2c865c17d088d2e215a8f4.jpg", + "alternates": {} + }, + "score": { + "up": 1, + "down": 0, + "total": 1 + }, + "tags": { + "general": [ + "accessory", + "anthro", + "bodily_fluids", + "breath", + "clothing", + "female", + "gym_clothing", + "gym_uniform", + "headband", + "panting", + "solo", + "sweat", + "tired", + "uniform" + ], + "artist": [ + "alpi" + ], + "copyright": [ + "undertale_(series)" + ], + "character": [ + "alphys" + ], + "species": [ + "dinosaur", + "reptile", + "scalie" + ], + "invalid": [], + "meta": [], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283522, + "flags": { + "pending": true, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "s", + "fav_count": 4, + "sources": [ + "https://twitter.com/ARUPUT_UT/status/1711177807500640314" + ], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 750607, + "description": "", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371072, + "created_at": "2023-10-24T09:08:43.730-04:00", + "updated_at": "2023-10-24T09:37:16.758-04:00", + "file": { + "width": 1300, + "height": 1318, + "ext": "jpg", + "size": 122061, + "md5": "17971d943808f9a886eda56c8d563e2a", + "url": "https://static1.e621.net/data/17/97/17971d943808f9a886eda56c8d563e2a.jpg" + }, + "preview": { + "width": 147, + "height": 150, + "url": "https://static1.e621.net/data/preview/17/97/17971d943808f9a886eda56c8d563e2a.jpg" + }, + "sample": { + "has": true, + "height": 861, + "width": 850, + "url": "https://static1.e621.net/data/sample/17/97/17971d943808f9a886eda56c8d563e2a.jpg", + "alternates": {} + }, + "score": { + "up": 1, + "down": 0, + "total": 1 + }, + "tags": { + "general": [ + "anthro", + "bikini", + "camel_toe", + "chubby_female", + "clothing", + "female", + "nipple_outline", + "solo", + "swimwear" + ], + "artist": [ + "alpi" + ], + "copyright": [ + "undertale_(series)" + ], + "character": [ + "alphys" + ], + "species": [ + "dinosaur", + "reptile", + "scalie" + ], + "invalid": [], + "meta": [ + "hi_res" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283518, + "flags": { + "pending": true, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "q", + "fav_count": 6, + "sources": [ + "https://twitter.com/ARUPUT_UT/status/1710684456330403944" + ], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 750607, + "description": "", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371071, + "created_at": "2023-10-24T09:08:01.189-04:00", + "updated_at": "2023-10-24T09:37:53.666-04:00", + "file": { + "width": 697, + "height": 1040, + "ext": "jpg", + "size": 67320, + "md5": "2d3c88639930feed12bb14aecda128a1", + "url": "https://static1.e621.net/data/2d/3c/2d3c88639930feed12bb14aecda128a1.jpg" + }, + "preview": { + "width": 100, + "height": 150, + "url": "https://static1.e621.net/data/preview/2d/3c/2d3c88639930feed12bb14aecda128a1.jpg" + }, + "sample": { + "has": false, + "height": 1040, + "width": 697, + "url": "https://static1.e621.net/data/2d/3c/2d3c88639930feed12bb14aecda128a1.jpg", + "alternates": {} + }, + "score": { + "up": 9, + "down": 0, + "total": 9 + }, + "tags": { + "general": [ + "anthro", + "bandage_on_nipple", + "big_breasts", + "blush", + "breasts", + "clothing", + "female", + "huge_breasts", + "milk", + "purple_eyes", + "solo", + "tail", + "text", + "thong", + "underwear", + "white_body" + ], + "artist": [ + "thedeathcrow05" + ], + "copyright": [ + "undertale", + "undertale_(series)" + ], + "character": [ + "toriel" + ], + "species": [ + "boss_monster", + "bovid", + "caprine", + "mammal" + ], + "invalid": [], + "meta": [ + "english_text" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283457, + "flags": { + "pending": true, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "e", + "fav_count": 17, + "sources": [ + "https://twitter.com/TheDeathCrow05/status/1612328447178858497" + ], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 1012446, + "description": "", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371070, + "created_at": "2023-10-24T09:05:15.350-04:00", + "updated_at": "2023-10-24T09:08:06.470-04:00", + "file": { + "width": 1481, + "height": 1462, + "ext": "png", + "size": 96562, + "md5": "e1874875d205ec2469b43f9d9b4b002a", + "url": "https://static1.e621.net/data/e1/87/e1874875d205ec2469b43f9d9b4b002a.png" + }, + "preview": { + "width": 150, + "height": 148, + "url": "https://static1.e621.net/data/preview/e1/87/e1874875d205ec2469b43f9d9b4b002a.jpg" + }, + "sample": { + "has": true, + "height": 839, + "width": 850, + "url": "https://static1.e621.net/data/sample/e1/87/e1874875d205ec2469b43f9d9b4b002a.jpg", + "alternates": {} + }, + "score": { + "up": 0, + "down": 0, + "total": 0 + }, + "tags": { + "general": [ + "ambiguous_gender", + "angry", + "border", + "city", + "city_background", + "dialogue", + "dragging_another", + "duo", + "eyes_closed", + "feral", + "flying", + "inside", + "licking", + "licking_another", + "looking_at_another", + "male", + "motion_lines", + "narrowed_eyes", + "one_eye_closed", + "open_mouth", + "outside", + "plant", + "sound_effects", + "speech_bubble", + "standing", + "sun", + "text", + "tongue", + "tongue_out", + "vowelless", + "vowelless_sound_effect", + "walking", + "white_border", + "yelling" + ], + "artist": [ + "shane_frost" + ], + "copyright": [], + "character": [ + "daniel_toke" + ], + "species": [ + "human", + "mammal", + "unknown_species" + ], + "invalid": [], + "meta": [ + "comic", + "english_text", + "greyscale", + "hi_res", + "monochrome" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283458, + "flags": { + "pending": true, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "s", + "fav_count": 0, + "sources": [ + "https://nl.ib.metapix.net/files/full/710/710848_callmedoc_a_bat_followed_me_home_page_7.png", + "https://inkbunny.net/s/542842", + "https://www.sofurry.com/view/682267", + "https://www.furaffinity.net/view/12435536", + "https://m.tapas.io/episode/167234" + ], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 732974, + "description": "Bastard! Returning to the scene of the crime. The crime being....It!\r\n[section,expanded=Tapas Description]\r\nBits and bats.\r\n[/section]\r\n", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371069, + "created_at": "2023-10-24T09:04:12.926-04:00", + "updated_at": "2023-10-24T09:16:32.652-04:00", + "file": { + "width": 960, + "height": 720, + "ext": "webm", + "size": 5158955, + "md5": "0460af671a7691e3273e0301d379f46a", + "url": "https://static1.e621.net/data/04/60/0460af671a7691e3273e0301d379f46a.webm" + }, + "preview": { + "width": 150, + "height": 112, + "url": "https://static1.e621.net/data/preview/04/60/0460af671a7691e3273e0301d379f46a.jpg" + }, + "sample": { + "has": true, + "height": 637, + "width": 850, + "url": "https://static1.e621.net/data/sample/04/60/0460af671a7691e3273e0301d379f46a.jpg", + "alternates": { + "480p": { + "type": "video", + "height": 480, + "width": 640, + "urls": [ + "https://static1.e621.net/data/sample/04/60/0460af671a7691e3273e0301d379f46a_480p.webm", + "https://static1.e621.net/data/sample/04/60/0460af671a7691e3273e0301d379f46a_480p.mp4" + ] + }, + "original": { + "type": "video", + "height": 720, + "width": 960, + "urls": [ + null, + "https://static1.e621.net/data/04/60/0460af671a7691e3273e0301d379f46a.mp4" + ] + } + } + }, + "score": { + "up": 2, + "down": -2, + "total": 0 + }, + "tags": { + "general": [ + "anthro", + "clothed", + "clothing", + "dancing", + "duo", + "gloves", + "hair", + "handwear", + "heart_attack", + "maid_uniform", + "male", + "mask", + "paw_gloves", + "tail", + "uniform" + ], + "artist": [ + "kminsi21" + ], + "copyright": [ + "arknights", + "hypergryph", + "sad_cat_dance", + "studio_montagne" + ], + "character": [ + "doctor_(arknights)", + "lee_(arknights)" + ], + "species": [ + "carp", + "cyprinid", + "cypriniform", + "dragon", + "fish", + "human", + "mammal", + "marine" + ], + "invalid": [], + "meta": [ + "animated", + "monochrome", + "sketch", + "sound", + "webm" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283449, + "flags": { + "pending": true, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "s", + "fav_count": 2, + "sources": [ + "https://twitter.com/kminsi21/status/1665583463099932673" + ], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 208393, + "description": "", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": 29.82 + }, + { + "id": 4371068, + "created_at": "2023-10-24T09:01:14.163-04:00", + "updated_at": "2023-10-24T09:16:07.126-04:00", + "file": { + "width": 1900, + "height": 1900, + "ext": "jpg", + "size": 131515, + "md5": "88276d3fc28ce54545e1c3fd0ec3ede7", + "url": "https://static1.e621.net/data/88/27/88276d3fc28ce54545e1c3fd0ec3ede7.jpg" + }, + "preview": { + "width": 150, + "height": 150, + "url": "https://static1.e621.net/data/preview/88/27/88276d3fc28ce54545e1c3fd0ec3ede7.jpg" + }, + "sample": { + "has": true, + "height": 850, + "width": 850, + "url": "https://static1.e621.net/data/sample/88/27/88276d3fc28ce54545e1c3fd0ec3ede7.jpg", + "alternates": {} + }, + "score": { + "up": 1, + "down": -1, + "total": 0 + }, + "tags": { + "general": [ + "anthro", + "big_breasts", + "black_eyes", + "breasts", + "female", + "food", + "gem", + "happy", + "huge_breasts", + "looking_at_another", + "plant", + "shrub", + "simple_background", + "slightly_chubby", + "smile", + "solo", + "star", + "thick_thighs", + "yellow_body" + ], + "artist": [ + "lightmizano" + ], + "copyright": [ + "mythology", + "puyo_puyo", + "sega" + ], + "character": [ + "carbuncle_(puyo_puyo)" + ], + "species": [ + "mythological_carbuncle" + ], + "invalid": [], + "meta": [ + "hi_res" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283448, + "flags": { + "pending": true, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "e", + "fav_count": 1, + "sources": [ + "https://twitter.com/SteveSt58244496/status/1716801139864125822" + ], + "pools": [ + 37105 + ], + "relationships": { + "parent_id": null, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 1012446, + "description": "", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371067, + "created_at": "2023-10-24T08:57:53.836-04:00", + "updated_at": "2023-10-24T09:38:59.008-04:00", + "file": { + "width": 1500, + "height": 2017, + "ext": "png", + "size": 4087670, + "md5": "8f5da1dd21c6a088e2dd0d724627bb56", + "url": "https://static1.e621.net/data/8f/5d/8f5da1dd21c6a088e2dd0d724627bb56.png" + }, + "preview": { + "width": 111, + "height": 150, + "url": "https://static1.e621.net/data/preview/8f/5d/8f5da1dd21c6a088e2dd0d724627bb56.jpg" + }, + "sample": { + "has": true, + "height": 1142, + "width": 850, + "url": "https://static1.e621.net/data/sample/8f/5d/8f5da1dd21c6a088e2dd0d724627bb56.jpg", + "alternates": {} + }, + "score": { + "up": 9, + "down": 0, + "total": 9 + }, + "tags": { + "general": [ + "anthro", + "clothed", + "clothing", + "cross", + "dress", + "duo", + "eyes_closed", + "female", + "hand_on_hip", + "hands_behind_back", + "legwear", + "nun", + "nun_habit", + "nun_outfit", + "religious_clothing", + "simple_background", + "smile", + "thigh_highs", + "torn_clothing" + ], + "artist": [ + "waspsalad" + ], + "copyright": [], + "character": [ + "almond_(waspsalad)", + "torpor_(waspsalad)" + ], + "species": [ + "deer", + "lagomorph", + "leporid", + "mammal", + "rabbit" + ], + "invalid": [], + "meta": [ + "hi_res", + "monochrome", + "sketch" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283436, + "flags": { + "pending": false, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "s", + "fav_count": 10, + "sources": [ + "https://twitter.com/waspsalads/status/1716601146830115030" + ], + "pools": [], + "relationships": { + "parent_id": 4369182, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 867661, + "description": "Compromise", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371066, + "created_at": "2023-10-24T08:56:32.447-04:00", + "updated_at": "2023-10-24T09:38:26.883-04:00", + "file": { + "width": 912, + "height": 1048, + "ext": "jpg", + "size": 70332, + "md5": "8fdd074811443f74d47a4c6b0f721262", + "url": "https://static1.e621.net/data/8f/dd/8fdd074811443f74d47a4c6b0f721262.jpg" + }, + "preview": { + "width": 130, + "height": 150, + "url": "https://static1.e621.net/data/preview/8f/dd/8fdd074811443f74d47a4c6b0f721262.jpg" + }, + "sample": { + "has": true, + "height": 976, + "width": 850, + "url": "https://static1.e621.net/data/sample/8f/dd/8fdd074811443f74d47a4c6b0f721262.jpg", + "alternates": {} + }, + "score": { + "up": 5, + "down": 0, + "total": 5 + }, + "tags": { + "general": [ + "anthro", + "big_breasts", + "big_butt", + "bikini", + "bikini_top", + "black_pupils", + "breasts", + "brown_body", + "butt", + "clothing", + "female", + "hair", + "holidays", + "legwear", + "nun", + "pupils", + "red_hair", + "smile", + "solo", + "stockings", + "swimwear", + "text", + "thong", + "underwear", + "yellow_eyes" + ], + "artist": [ + "thedeathcrow05" + ], + "copyright": [ + "new_year" + ], + "character": [], + "species": [ + "canid", + "canine", + "canis", + "domestic_dog", + "mammal" + ], + "invalid": [], + "meta": [ + "english_text" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283433, + "flags": { + "pending": true, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "e", + "fav_count": 8, + "sources": [ + "https://twitter.com/TheDeathCrow05/status/1609642102438117380" + ], + "pools": [], + "relationships": { + "parent_id": 4371065, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 1012446, + "description": "", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371065, + "created_at": "2023-10-24T08:56:23.134-04:00", + "updated_at": "2023-10-24T09:26:57.157-04:00", + "file": { + "width": 912, + "height": 1195, + "ext": "jpg", + "size": 70686, + "md5": "e3049ab4cccc945f65bb473dcac150ef", + "url": "https://static1.e621.net/data/e3/04/e3049ab4cccc945f65bb473dcac150ef.jpg" + }, + "preview": { + "width": 114, + "height": 150, + "url": "https://static1.e621.net/data/preview/e3/04/e3049ab4cccc945f65bb473dcac150ef.jpg" + }, + "sample": { + "has": true, + "height": 1113, + "width": 850, + "url": "https://static1.e621.net/data/sample/e3/04/e3049ab4cccc945f65bb473dcac150ef.jpg", + "alternates": {} + }, + "score": { + "up": 1, + "down": 0, + "total": 1 + }, + "tags": { + "general": [ + "anthro", + "big_breasts", + "big_butt", + "black_pupils", + "breasts", + "brown_body", + "butt", + "clothing", + "female", + "nun", + "pupils", + "smile", + "solo", + "thong", + "torn_clothing", + "tunic", + "underwear", + "yellow_eyes" + ], + "artist": [ + "thedeathcrow05" + ], + "copyright": [], + "character": [], + "species": [ + "canid", + "canine", + "canis", + "domestic_dog", + "mammal" + ], + "invalid": [], + "meta": [], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283434, + "flags": { + "pending": true, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "q", + "fav_count": 5, + "sources": [ + "https://twitter.com/TheDeathCrow05/status/1609642102438117380" + ], + "pools": [], + "relationships": { + "parent_id": 4371064, + "has_children": true, + "has_active_children": true, + "children": [ + 4371066 + ] + }, + "approver_id": null, + "uploader_id": 1012446, + "description": "", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371064, + "created_at": "2023-10-24T08:56:12.660-04:00", + "updated_at": "2023-10-24T09:20:58.426-04:00", + "file": { + "width": 912, + "height": 1195, + "ext": "jpg", + "size": 52928, + "md5": "86a7c29047c4f141b3abe568069a2de0", + "url": "https://static1.e621.net/data/86/a7/86a7c29047c4f141b3abe568069a2de0.jpg" + }, + "preview": { + "width": 114, + "height": 150, + "url": "https://static1.e621.net/data/preview/86/a7/86a7c29047c4f141b3abe568069a2de0.jpg" + }, + "sample": { + "has": true, + "height": 1113, + "width": 850, + "url": "https://static1.e621.net/data/sample/86/a7/86a7c29047c4f141b3abe568069a2de0.jpg", + "alternates": {} + }, + "score": { + "up": 1, + "down": 0, + "total": 1 + }, + "tags": { + "general": [ + "anthro", + "big_breasts", + "big_butt", + "black_pupils", + "breasts", + "brown_body", + "butt", + "clothing", + "female", + "nun", + "nun_outfit", + "pupils", + "smile", + "solo", + "tunic", + "yellow_eyes" + ], + "artist": [ + "thedeathcrow05" + ], + "copyright": [], + "character": [], + "species": [ + "canid", + "canine", + "canis", + "domestic_dog", + "mammal" + ], + "invalid": [], + "meta": [], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283432, + "flags": { + "pending": true, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "q", + "fav_count": 4, + "sources": [ + "https://twitter.com/TheDeathCrow05/status/1609642102438117380" + ], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": true, + "has_active_children": true, + "children": [ + 4371065 + ] + }, + "approver_id": null, + "uploader_id": 1012446, + "description": "", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371063, + "created_at": "2023-10-24T08:54:54.522-04:00", + "updated_at": "2023-10-24T09:08:19.973-04:00", + "file": { + "width": 2535, + "height": 2108, + "ext": "png", + "size": 3290418, + "md5": "1f2e235b8756d3cf946daa75a4a454eb", + "url": "https://static1.e621.net/data/1f/2e/1f2e235b8756d3cf946daa75a4a454eb.png" + }, + "preview": { + "width": 150, + "height": 124, + "url": "https://static1.e621.net/data/preview/1f/2e/1f2e235b8756d3cf946daa75a4a454eb.jpg" + }, + "sample": { + "has": true, + "height": 706, + "width": 850, + "url": "https://static1.e621.net/data/sample/1f/2e/1f2e235b8756d3cf946daa75a4a454eb.jpg", + "alternates": {} + }, + "score": { + "up": 3, + "down": 0, + "total": 3 + }, + "tags": { + "general": [ + "5_toes", + "anal", + "anal_penetration", + "anthro", + "anus", + "balls", + "bodily_fluids", + "breasts", + "butt", + "clothing", + "cum", + "cum_inside", + "dominant", + "dominant_gynomorph", + "dominant_intersex", + "duo", + "feet", + "genital_fluids", + "genitals", + "gynomorph", + "gynomorph/male", + "gynomorph_penetrating", + "gynomorph_penetrating_male", + "intersex", + "intersex/male", + "intersex_penetrating", + "intersex_penetrating_male", + "legwear", + "male", + "male_penetrated", + "penetration", + "sex", + "small_breasts", + "stockings", + "toes", + "transgender_penetrating_male" + ], + "artist": [ + "clara_(artist)" + ], + "copyright": [], + "character": [ + "sofia_chandler" + ], + "species": [ + "domestic_cat", + "felid", + "feline", + "felis", + "mammal", + "serval" + ], + "invalid": [], + "meta": [ + "hi_res" + ], + "lore": [ + "trans_(lore)", + "trans_woman_(lore)" + ] + }, + "locked_tags": [], + "change_seq": 51283426, + "flags": { + "pending": true, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "e", + "fav_count": 3, + "sources": [ + "https://www.furaffinity.net/view/54168715/" + ], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 821547, + "description": "", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371062, + "created_at": "2023-10-24T08:50:05.608-04:00", + "updated_at": "2023-10-24T09:38:12.739-04:00", + "file": { + "width": 912, + "height": 1172, + "ext": "jpg", + "size": 76096, + "md5": "58333ce2afbd938e0c66d9f8a1e417cd", + "url": "https://static1.e621.net/data/58/33/58333ce2afbd938e0c66d9f8a1e417cd.jpg" + }, + "preview": { + "width": 116, + "height": 150, + "url": "https://static1.e621.net/data/preview/58/33/58333ce2afbd938e0c66d9f8a1e417cd.jpg" + }, + "sample": { + "has": true, + "height": 1092, + "width": 850, + "url": "https://static1.e621.net/data/sample/58/33/58333ce2afbd938e0c66d9f8a1e417cd.jpg", + "alternates": {} + }, + "score": { + "up": 6, + "down": 0, + "total": 6 + }, + "tags": { + "general": [ + "anthro", + "big_butt", + "black_eyes", + "butt", + "clothed", + "clothing", + "eyewear", + "female", + "glasses", + "hair", + "legwear", + "long_neck", + "red_body", + "red_hair", + "shirt", + "simple_background", + "solo", + "stockings", + "tail", + "thong", + "topwear", + "underwear" + ], + "artist": [ + "thedeathcrow05" + ], + "copyright": [], + "character": [ + "elyssa_(trinity-fate62)" + ], + "species": [ + "reptile", + "scalie", + "snake" + ], + "invalid": [], + "meta": [], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283413, + "flags": { + "pending": true, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "q", + "fav_count": 15, + "sources": [ + "https://twitter.com/TheDeathCrow05/status/1608975999290707969" + ], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 1012446, + "description": "", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371061, + "created_at": "2023-10-24T08:46:50.590-04:00", + "updated_at": "2023-10-24T09:40:39.289-04:00", + "file": { + "width": 3840, + "height": 2160, + "ext": "png", + "size": 8981487, + "md5": "ebb0f91e4ca37af3bc807d999c71436c", + "url": "https://static1.e621.net/data/eb/b0/ebb0f91e4ca37af3bc807d999c71436c.png" + }, + "preview": { + "width": 150, + "height": 84, + "url": "https://static1.e621.net/data/preview/eb/b0/ebb0f91e4ca37af3bc807d999c71436c.jpg" + }, + "sample": { + "has": true, + "height": 478, + "width": 850, + "url": "https://static1.e621.net/data/sample/eb/b0/ebb0f91e4ca37af3bc807d999c71436c.jpg", + "alternates": {} + }, + "score": { + "up": 6, + "down": 0, + "total": 6 + }, + "tags": { + "general": [ + "2_penises", + "animal_genitalia", + "animal_penis", + "anus", + "bodily_fluids", + "butt", + "claws", + "erection", + "feral", + "fur", + "genital_fluids", + "genitals", + "hemipenes", + "horn", + "jewelry", + "looking_at_viewer", + "looking_back", + "looking_back_at_viewer", + "male", + "membrane_(anatomy)", + "membranous_wings", + "multi_genitalia", + "multi_penis", + "paws", + "penile", + "penile_spines", + "penis", + "pink_penis", + "puffy_anus", + "rear_view", + "red_body", + "red_fur", + "red_scales", + "ring", + "scales", + "solo", + "tail", + "wings" + ], + "artist": [ + "glaredendriago" + ], + "copyright": [], + "character": [ + "entropy_(billeur)" + ], + "species": [ + "dragon", + "scalie", + "western_dragon" + ], + "invalid": [], + "meta": [ + "absurd_res", + "hi_res" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283407, + "flags": { + "pending": true, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "e", + "fav_count": 15, + "sources": [ + "https://twitter.com/GlaredEndriago/status/1703583303222071357", + "https://www.furaffinity.net/view/53729256/" + ], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 1124811, + "description": "", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371060, + "created_at": "2023-10-24T08:40:38.177-04:00", + "updated_at": "2023-10-24T09:07:32.371-04:00", + "file": { + "width": 3000, + "height": 3000, + "ext": "png", + "size": 15331744, + "md5": "c97c2387619f3121031081687e9a26dd", + "url": "https://static1.e621.net/data/c9/7c/c97c2387619f3121031081687e9a26dd.png" + }, + "preview": { + "width": 150, + "height": 150, + "url": "https://static1.e621.net/data/preview/c9/7c/c97c2387619f3121031081687e9a26dd.jpg" + }, + "sample": { + "has": true, + "height": 850, + "width": 850, + "url": "https://static1.e621.net/data/sample/c9/7c/c97c2387619f3121031081687e9a26dd.jpg", + "alternates": {} + }, + "score": { + "up": 4, + "down": 0, + "total": 4 + }, + "tags": { + "general": [ + "4_toes", + "anthro", + "band-aid", + "band-aid_on_face", + "band-aid_on_nose", + "bandage", + "black_body", + "black_fur", + "cheek_tuft", + "chest_tuft", + "clothing", + "facial_tuft", + "feet", + "fur", + "grey_body", + "grey_fur", + "half-closed_eyes", + "male", + "narrowed_eyes", + "pink_background", + "simple_background", + "solo", + "swimming_trunks", + "swimwear", + "toes", + "tuft" + ], + "artist": [ + "xing1" + ], + "copyright": [], + "character": [ + "nick_(the_xing1)" + ], + "species": [ + "lagomorph", + "leporid", + "mammal", + "rabbit" + ], + "invalid": [], + "meta": [ + "absurd_res", + "hi_res" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283400, + "flags": { + "pending": true, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "s", + "fav_count": 5, + "sources": [ + "https://discord.com/channels/981853435680915486/982017622830895204/1166318108730982471" + ], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 567598, + "description": "https://ko-fi.com/niixart", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371059, + "created_at": "2023-10-24T08:40:24.593-04:00", + "updated_at": "2023-10-24T09:07:27.182-04:00", + "file": { + "width": 1466, + "height": 1894, + "ext": "png", + "size": 134148, + "md5": "91d24fcfb2e8ecfe85d5a9ecacc7671f", + "url": "https://static1.e621.net/data/91/d2/91d24fcfb2e8ecfe85d5a9ecacc7671f.png" + }, + "preview": { + "width": 116, + "height": 150, + "url": "https://static1.e621.net/data/preview/91/d2/91d24fcfb2e8ecfe85d5a9ecacc7671f.jpg" + }, + "sample": { + "has": true, + "height": 1098, + "width": 850, + "url": "https://static1.e621.net/data/sample/91/d2/91d24fcfb2e8ecfe85d5a9ecacc7671f.jpg", + "alternates": {} + }, + "score": { + "up": 0, + "down": 0, + "total": 0 + }, + "tags": { + "general": [ + "bodily_fluids", + "border", + "cabinet", + "city", + "city_background", + "cloud", + "container", + "dialogue", + "doorframe", + "doorway", + "duo", + "eye_contact", + "frown", + "furniture", + "holding_object", + "hospital", + "human_only", + "iconography", + "inside", + "looking_at_another", + "male", + "motion_lines", + "narrowed_eyes", + "not_furry", + "outside", + "poster", + "smile", + "speech_bubble", + "sun", + "sweat", + "sweatdrop", + "syringe", + "table", + "text", + "wall_(structure)", + "white_border", + "window", + "worried" + ], + "artist": [ + "shane_frost" + ], + "copyright": [], + "character": [ + "daniel_toke" + ], + "species": [ + "human", + "mammal" + ], + "invalid": [], + "meta": [ + "comic", + "english_text", + "greyscale", + "hi_res", + "monochrome" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283420, + "flags": { + "pending": true, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "s", + "fav_count": 1, + "sources": [ + "https://nl.ib.metapix.net/files/full/710/710478_callmedoc_a_bat_followed_me_home_page_6.png", + "https://inkbunny.net/s/542588", + "https://www.sofurry.com/view/682018", + "https://www.furaffinity.net/view/12431693", + "https://m.tapas.io/episode/167231" + ], + "pools": [ + 37145 + ], + "relationships": { + "parent_id": null, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 732974, + "description": "LOL pain and crippling debt.\r\n[section,expanded=Tapas Description]\r\nDoctor Richards enjoys his patient. He's articulate, and he's always smiling. Smiling when Mr. Toke comes his way.\r\n[/section]", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371058, + "created_at": "2023-10-24T08:40:16.612-04:00", + "updated_at": "2023-10-24T09:37:21.025-04:00", + "file": { + "width": 2060, + "height": 1789, + "ext": "png", + "size": 2858203, + "md5": "31ed336814a1445629ae021a7e48af79", + "url": "https://static1.e621.net/data/31/ed/31ed336814a1445629ae021a7e48af79.png" + }, + "preview": { + "width": 150, + "height": 130, + "url": "https://static1.e621.net/data/preview/31/ed/31ed336814a1445629ae021a7e48af79.jpg" + }, + "sample": { + "has": true, + "height": 738, + "width": 850, + "url": "https://static1.e621.net/data/sample/31/ed/31ed336814a1445629ae021a7e48af79.jpg", + "alternates": {} + }, + "score": { + "up": 4, + "down": -2, + "total": 2 + }, + "tags": { + "general": [ + "anal", + "anal_penetration", + "anthro", + "balls", + "big_balls", + "big_penis", + "bodily_fluids", + "cum", + "cum_on_ground", + "dragging", + "ejaculation", + "genital_fluids", + "genitals", + "holding_object", + "holding_phone", + "huge_balls", + "huge_penis", + "hyper", + "hyper_balls", + "hyper_genitalia", + "hyper_penis", + "leaking_cum", + "male", + "object_in_ass", + "penetration", + "penis", + "phone", + "sex_toy", + "sex_toy_in_ass", + "sex_toy_insertion", + "solo", + "vibrator", + "vibrator_in_ass", + "walking" + ], + "artist": [ + "lotolotl" + ], + "copyright": [ + "animal_crossing", + "nintendo" + ], + "character": [ + "ed_(animal_crossing)" + ], + "species": [ + "equid", + "equine", + "horse", + "mammal" + ], + "invalid": [], + "meta": [ + "hi_res" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283571, + "flags": { + "pending": false, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "e", + "fav_count": 5, + "sources": [ + "https://www.furaffinity.net/view/54101914/", + "https://d.furaffinity.net/art/lotolotl/1697669198/1697669198.lotolotl_aced.png" + ], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": 95927, + "uploader_id": 919461, + "description": "Ed likes to take morning walks to let it all out. We can't blame him for the mess he always makes everywhere. He's used to it and doesn't worry a bit, as he should. What's to worry about his beautiful, overgrown genitals he's proud of? In fact, we should all be grateful for it. He also never leaves the house without a vibrator up his ass. It adds to the fun ^^\r\n\r\nAnyone looking into moving to a village where cum showers are an everyday thing? X3", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371057, + "created_at": "2023-10-24T08:33:53.314-04:00", + "updated_at": "2023-10-24T08:36:40.088-04:00", + "file": { + "width": 3072, + "height": 4080, + "ext": "png", + "size": 628996, + "md5": "9a07506d25f34f90796bbc36d817d8c1", + "url": "https://static1.e621.net/data/9a/07/9a07506d25f34f90796bbc36d817d8c1.png" + }, + "preview": { + "width": 112, + "height": 150, + "url": "https://static1.e621.net/data/preview/9a/07/9a07506d25f34f90796bbc36d817d8c1.jpg" + }, + "sample": { + "has": true, + "height": 1128, + "width": 850, + "url": "https://static1.e621.net/data/sample/9a/07/9a07506d25f34f90796bbc36d817d8c1.jpg", + "alternates": {} + }, + "score": { + "up": 1, + "down": 0, + "total": 1 + }, + "tags": { + "general": [ + "amulet", + "anthro", + "bangs", + "black_hair", + "breasts", + "casual_nudity", + "chibi", + "clothing", + "female", + "footwear", + "goth", + "hair", + "half-closed_eyes", + "highlights_(coloring)", + "looking_at_viewer", + "narrowed_eyes", + "nipple_piercing", + "nipple_ring", + "nipples", + "nude", + "piercing", + "purple_highlights", + "ring_piercing", + "shoes", + "solo", + "staff", + "tasteful_nudity" + ], + "artist": [ + "bludraconoid" + ], + "copyright": [ + "disney", + "ducktales", + "ducktales_(2017)" + ], + "character": [ + "magica_de_spell" + ], + "species": [ + "anatid", + "anseriform", + "avian", + "bird", + "duck" + ], + "invalid": [], + "meta": [ + "absurd_res", + "hi_res" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283381, + "flags": { + "pending": true, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "q", + "fav_count": 1, + "sources": [ + "https://twitter.com/bludraconoid", + "https://deviantart.com/bludraconoid", + "https://www.furaffinity.net/user/imaginespacedragons" + ], + "pools": [], + "relationships": { + "parent_id": 4371056, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 901938, + "description": "", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371056, + "created_at": "2023-10-24T08:32:46.428-04:00", + "updated_at": "2023-10-24T08:33:53.359-04:00", + "file": { + "width": 3072, + "height": 4080, + "ext": "png", + "size": 899195, + "md5": "4503495a8f70033ddc612839e5c07a40", + "url": "https://static1.e621.net/data/45/03/4503495a8f70033ddc612839e5c07a40.png" + }, + "preview": { + "width": 112, + "height": 150, + "url": "https://static1.e621.net/data/preview/45/03/4503495a8f70033ddc612839e5c07a40.jpg" + }, + "sample": { + "has": true, + "height": 1128, + "width": 850, + "url": "https://static1.e621.net/data/sample/45/03/4503495a8f70033ddc612839e5c07a40.jpg", + "alternates": {} + }, + "score": { + "up": 1, + "down": 0, + "total": 1 + }, + "tags": { + "general": [ + "amulet", + "anthro", + "bangs", + "black_hair", + "breasts", + "chibi", + "cleavage", + "clothed", + "clothing", + "dress", + "female", + "footwear", + "goth", + "hair", + "half-closed_eyes", + "highlights_(coloring)", + "looking_at_viewer", + "narrowed_eyes", + "purple_highlights", + "shoes", + "solo", + "staff" + ], + "artist": [ + "bludraconoid" + ], + "copyright": [ + "disney", + "ducktales", + "ducktales_(2017)" + ], + "character": [ + "magica_de_spell" + ], + "species": [ + "anatid", + "anseriform", + "avian", + "bird", + "duck" + ], + "invalid": [], + "meta": [ + "absurd_res", + "hi_res" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283382, + "flags": { + "pending": true, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "s", + "fav_count": 0, + "sources": [ + "https://twitter.com/bludraconoid", + "https://deviantart.com/bludraconoid", + "https://www.furaffinity.net/user/imaginespacedragons" + ], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": true, + "has_active_children": true, + "children": [ + 4371057 + ] + }, + "approver_id": null, + "uploader_id": 901938, + "description": "", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371055, + "created_at": "2023-10-24T08:29:09.969-04:00", + "updated_at": "2023-10-24T09:33:55.400-04:00", + "file": { + "width": 3200, + "height": 4622, + "ext": "png", + "size": 10234888, + "md5": "4fb13574edd8440ae39b9abfc094a645", + "url": "https://static1.e621.net/data/4f/b1/4fb13574edd8440ae39b9abfc094a645.png" + }, + "preview": { + "width": 103, + "height": 150, + "url": "https://static1.e621.net/data/preview/4f/b1/4fb13574edd8440ae39b9abfc094a645.jpg" + }, + "sample": { + "has": true, + "height": 1227, + "width": 850, + "url": "https://static1.e621.net/data/sample/4f/b1/4fb13574edd8440ae39b9abfc094a645.jpg", + "alternates": {} + }, + "score": { + "up": 10, + "down": 0, + "total": 10 + }, + "tags": { + "general": [ + "abdominal_bulge", + "accessory", + "anal", + "anal_penetration", + "anthro", + "aquamarine_eyes", + "aquamarine_fur", + "black_body", + "black_scales", + "bodily_fluids", + "bottomwear", + "butt", + "clothing", + "cum", + "ear_piercing", + "ear_ring", + "forced", + "full_nelson", + "furgonomics", + "genital_fluids", + "genitals", + "hair", + "jewelry", + "male", + "male/male", + "mohawk", + "pants", + "penetration", + "penis", + "piercing", + "ponytail", + "rape", + "ring", + "ring_piercing", + "scales", + "sharp_teeth", + "size_difference", + "slit", + "sweat", + "tail", + "tail_accessory", + "tail_jewelry", + "tail_ring", + "tears", + "teeth", + "thick_tail", + "tongue", + "tongue_out", + "torn_bottomwear", + "torn_clothing", + "torn_pants", + "white_hair", + "wings" + ], + "artist": [ + "haiyan" + ], + "copyright": [], + "character": [ + "aden_rakuraku" + ], + "species": [ + "canid", + "canine", + "canis", + "dinosaur", + "domestic_dog", + "dragon", + "hybrid", + "mammal", + "reptile", + "scalie", + "theropod", + "tyrannosaurid", + "tyrannosaurus", + "tyrannosaurus_rex" + ], + "invalid": [], + "meta": [ + "absurd_res", + "hi_res" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283372, + "flags": { + "pending": true, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "e", + "fav_count": 12, + "sources": [ + "https://twitter.com/Ian_nyang666/status/1716108145498742952?t=kULzgVCkKZNUYbOBlpirqA\u0026s=19" + ], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 515866, + "description": "Aden Rakuraku, a young dragondog adventurer, is being eaten by T-Rex❤️❤️❤️\r\n\r\n\r\nAden's character designer: @risuou(https://x.com/risuou?t=c_lIIz5CYyAc7GNz8fJTeA\u0026s=09)\r\n\r\nArt by: IAN(https://x.com/risuou?t=c_lIIz5CYyAc7GNz8fJTeA\u0026s=09)", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371054, + "created_at": "2023-10-24T08:28:37.898-04:00", + "updated_at": "2023-10-24T08:44:05.729-04:00", + "file": { + "width": 3688, + "height": 3508, + "ext": "png", + "size": 2829928, + "md5": "880798b7988d6ade68e4bcc23d7c62d2", + "url": "https://static1.e621.net/data/88/07/880798b7988d6ade68e4bcc23d7c62d2.png" + }, + "preview": { + "width": 150, + "height": 142, + "url": "https://static1.e621.net/data/preview/88/07/880798b7988d6ade68e4bcc23d7c62d2.jpg" + }, + "sample": { + "has": true, + "height": 808, + "width": 850, + "url": "https://static1.e621.net/data/sample/88/07/880798b7988d6ade68e4bcc23d7c62d2.jpg", + "alternates": {} + }, + "score": { + "up": 1, + "down": 0, + "total": 1 + }, + "tags": { + "general": [ + "ahoge", + "ambiguous_gender", + "big_ears", + "clothed", + "clothing", + "grey_background", + "hair", + "hood", + "humanoid_pointy_ears", + "looking_at_viewer", + "short_hair", + "simple_background", + "solo", + "text" + ], + "artist": [ + "critterstew" + ], + "copyright": [], + "character": [], + "species": [ + "goblin", + "humanoid" + ], + "invalid": [], + "meta": [ + "absurd_res", + "hi_res", + "monochrome" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283370, + "flags": { + "pending": true, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "s", + "fav_count": 1, + "sources": [ + "https://twitter.com/critterstew/status/1710792505401204762" + ], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 1006937, + "description": "Gobtober day 8 - Cleric. Decided to do a very quick portrait of a little crazy person. Bit of religious zealotry.", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371053, + "created_at": "2023-10-24T08:27:21.979-04:00", + "updated_at": "2023-10-24T09:28:17.102-04:00", + "file": { + "width": 1024, + "height": 1024, + "ext": "png", + "size": 676521, + "md5": "2191a823053c5603f6e170bffdf36dee", + "url": "https://static1.e621.net/data/21/91/2191a823053c5603f6e170bffdf36dee.png" + }, + "preview": { + "width": 150, + "height": 150, + "url": "https://static1.e621.net/data/preview/21/91/2191a823053c5603f6e170bffdf36dee.jpg" + }, + "sample": { + "has": true, + "height": 850, + "width": 850, + "url": "https://static1.e621.net/data/sample/21/91/2191a823053c5603f6e170bffdf36dee.jpg", + "alternates": {} + }, + "score": { + "up": 5, + "down": -1, + "total": 4 + }, + "tags": { + "general": [ + "anal_hair", + "anthro", + "anus", + "big_butt", + "bodily_fluids", + "body_hair", + "butt", + "fart", + "fart_fetish", + "male", + "presenting", + "presenting_hindquarters", + "puffy_anus", + "solo", + "sweat", + "text" + ], + "artist": [ + "niallz_(artist)" + ], + "copyright": [ + "nintendo", + "pokemon" + ], + "character": [], + "species": [ + "canid", + "canine", + "canis", + "generation_7_pokemon", + "lycanroc", + "mammal", + "midnight_lycanroc", + "pokemon_(species)", + "were", + "werecanid", + "werecanine", + "werewolf", + "wolf" + ], + "invalid": [], + "meta": [], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283406, + "flags": { + "pending": true, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "e", + "fav_count": 11, + "sources": [ + "https://www.furaffinity.net/view/54168534/", + "https://twitter.com/StankyGhost/status/1716758920125935965" + ], + "pools": [], + "relationships": { + "parent_id": 4371052, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 150027, + "description": "", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371052, + "created_at": "2023-10-24T08:27:17.249-04:00", + "updated_at": "2023-10-24T09:38:09.572-04:00", + "file": { + "width": 1024, + "height": 1024, + "ext": "png", + "size": 618963, + "md5": "4357089f087f3b36bd272a636be90bcb", + "url": "https://static1.e621.net/data/43/57/4357089f087f3b36bd272a636be90bcb.png" + }, + "preview": { + "width": 150, + "height": 150, + "url": "https://static1.e621.net/data/preview/43/57/4357089f087f3b36bd272a636be90bcb.jpg" + }, + "sample": { + "has": true, + "height": 850, + "width": 850, + "url": "https://static1.e621.net/data/sample/43/57/4357089f087f3b36bd272a636be90bcb.jpg", + "alternates": {} + }, + "score": { + "up": 3, + "down": 0, + "total": 3 + }, + "tags": { + "general": [ + "anal_hair", + "anthro", + "anus", + "big_butt", + "bodily_fluids", + "body_hair", + "butt", + "fart", + "fart_fetish", + "male", + "presenting", + "presenting_hindquarters", + "puffy_anus", + "solo", + "sweat" + ], + "artist": [ + "niallz_(artist)" + ], + "copyright": [ + "nintendo", + "pokemon" + ], + "character": [], + "species": [ + "canid", + "canine", + "canis", + "generation_7_pokemon", + "lycanroc", + "mammal", + "midnight_lycanroc", + "pokemon_(species)", + "were", + "werecanid", + "werecanine", + "werewolf", + "wolf" + ], + "invalid": [], + "meta": [], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283581, + "flags": { + "pending": false, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "e", + "fav_count": 4, + "sources": [ + "https://www.furaffinity.net/view/54168530/", + "https://twitter.com/StankyGhost/status/1716758920125935965" + ], + "pools": [], + "relationships": { + "parent_id": 4371051, + "has_children": true, + "has_active_children": true, + "children": [ + 4371053 + ] + }, + "approver_id": 95927, + "uploader_id": 150027, + "description": "", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371051, + "created_at": "2023-10-24T08:27:10.027-04:00", + "updated_at": "2023-10-24T09:38:08.118-04:00", + "file": { + "width": 1024, + "height": 1024, + "ext": "png", + "size": 369688, + "md5": "c8be5db335140759ffa2451e364dacef", + "url": "https://static1.e621.net/data/c8/be/c8be5db335140759ffa2451e364dacef.png" + }, + "preview": { + "width": 150, + "height": 150, + "url": "https://static1.e621.net/data/preview/c8/be/c8be5db335140759ffa2451e364dacef.jpg" + }, + "sample": { + "has": true, + "height": 850, + "width": 850, + "url": "https://static1.e621.net/data/sample/c8/be/c8be5db335140759ffa2451e364dacef.jpg", + "alternates": {} + }, + "score": { + "up": 6, + "down": 0, + "total": 6 + }, + "tags": { + "general": [ + "anal_hair", + "anthro", + "anus", + "big_butt", + "bodily_fluids", + "body_hair", + "butt", + "male", + "presenting", + "presenting_hindquarters", + "puffy_anus", + "solo", + "sweat" + ], + "artist": [ + "niallz_(artist)" + ], + "copyright": [ + "nintendo", + "pokemon" + ], + "character": [], + "species": [ + "canid", + "canine", + "canis", + "generation_7_pokemon", + "lycanroc", + "mammal", + "midnight_lycanroc", + "pokemon_(species)", + "were", + "werecanid", + "werecanine", + "werewolf", + "wolf" + ], + "invalid": [], + "meta": [], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283580, + "flags": { + "pending": false, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "e", + "fav_count": 18, + "sources": [ + "https://www.furaffinity.net/view/54168521/", + "https://twitter.com/StankyGhost/status/1716758713048989973" + ], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": true, + "has_active_children": true, + "children": [ + 4371052 + ] + }, + "approver_id": 95927, + "uploader_id": 150027, + "description": "", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371050, + "created_at": "2023-10-24T08:26:26.901-04:00", + "updated_at": "2023-10-24T09:40:47.154-04:00", + "file": { + "width": 800, + "height": 540, + "ext": "png", + "size": 4807, + "md5": "ed8a20292614f4a28d49dd098d04fcb7", + "url": "https://static1.e621.net/data/ed/8a/ed8a20292614f4a28d49dd098d04fcb7.png" + }, + "preview": { + "width": 150, + "height": 101, + "url": "https://static1.e621.net/data/preview/ed/8a/ed8a20292614f4a28d49dd098d04fcb7.jpg" + }, + "sample": { + "has": false, + "height": 540, + "width": 800, + "url": "https://static1.e621.net/data/ed/8a/ed8a20292614f4a28d49dd098d04fcb7.png", + "alternates": {} + }, + "score": { + "up": 4, + "down": -1, + "total": 3 + }, + "tags": { + "general": [ + "ambiguous_gender", + "anthro", + "bedroom_eyes", + "feral", + "food", + "fruit", + "narrowed_eyes", + "plant", + "pumpkin", + "pumpkin_head", + "ratio", + "seductive", + "smug", + "smug_face", + "solo", + "standing" + ], + "artist": [ + "rbxgemini" + ], + "copyright": [], + "character": [ + "pumkat" + ], + "species": [ + "domestic_cat", + "elemental_creature", + "felid", + "feline", + "felis", + "flora_fauna", + "food_creature", + "mammal" + ], + "invalid": [], + "meta": [], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283411, + "flags": { + "pending": true, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "s", + "fav_count": 8, + "sources": [ + "https://twitter.com/FIKxGemini/status/1714771076490645593?t=VKRAJb3h1YrIweNZ9NCRAw\u0026s=19" + ], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 687020, + "description": "l + ratio lol got'em", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371049, + "created_at": "2023-10-24T08:25:36.131-04:00", + "updated_at": "2023-10-24T09:07:16.222-04:00", + "file": { + "width": 864, + "height": 1140, + "ext": "png", + "size": 251278, + "md5": "a79a8b32a87c23a4fb7b95685f7bb187", + "url": "https://static1.e621.net/data/a7/9a/a79a8b32a87c23a4fb7b95685f7bb187.png" + }, + "preview": { + "width": 113, + "height": 150, + "url": "https://static1.e621.net/data/preview/a7/9a/a79a8b32a87c23a4fb7b95685f7bb187.jpg" + }, + "sample": { + "has": true, + "height": 1121, + "width": 850, + "url": "https://static1.e621.net/data/sample/a7/9a/a79a8b32a87c23a4fb7b95685f7bb187.jpg", + "alternates": {} + }, + "score": { + "up": 2, + "down": -1, + "total": 1 + }, + "tags": { + "general": [ + "female", + "machine", + "monster_girl_(genre)", + "motorcycle", + "motorcycle_helmet", + "orange_body", + "orange_skin", + "solo", + "vehicle", + "white_body", + "white_skin" + ], + "artist": [ + "rottentuttifrutti" + ], + "copyright": [], + "character": [], + "species": [ + "humanoid", + "living_machine", + "living_vehicle", + "robot", + "robot_humanoid", + "vehicle_humanoid" + ], + "invalid": [], + "meta": [], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283355, + "flags": { + "pending": true, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "q", + "fav_count": 1, + "sources": [ + "https://www.deviantart.com/rottentuttifrutti/art/Mototaura-990064826", + "https://www.furaffinity.net/view/54169144/", + "https://twitter.com/RottenTutti/status/1716792936090726413", + "https://www.pixiv.net/artworks/112819576" + ], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 439608, + "description": "Species:\r\n Engine\r\nTypes:\r\n Automata\r\nDanger lv.:\r\n Low", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371048, + "created_at": "2023-10-24T08:23:20.440-04:00", + "updated_at": "2023-10-24T09:40:54.395-04:00", + "file": { + "width": 2000, + "height": 1290, + "ext": "png", + "size": 3545595, + "md5": "38d0929dfe92b16db0754b6af071c302", + "url": "https://static1.e621.net/data/38/d0/38d0929dfe92b16db0754b6af071c302.png" + }, + "preview": { + "width": 150, + "height": 96, + "url": "https://static1.e621.net/data/preview/38/d0/38d0929dfe92b16db0754b6af071c302.jpg" + }, + "sample": { + "has": true, + "height": 548, + "width": 850, + "url": "https://static1.e621.net/data/sample/38/d0/38d0929dfe92b16db0754b6af071c302.jpg", + "alternates": {} + }, + "score": { + "up": 11, + "down": 0, + "total": 11 + }, + "tags": { + "general": [ + "anthro", + "areola", + "bodily_fluids", + "clothed", + "clothing", + "cum", + "genital_fluids", + "gynomorph", + "hair", + "hair_over_eye", + "intersex", + "lactating", + "legwear", + "nipples", + "one_eye_obstructed", + "overweight", + "overweight_gynomorph", + "overweight_intersex", + "panties", + "sweat", + "thick_thighs", + "thigh_highs", + "topless", + "underwear" + ], + "artist": [ + "chromapan" + ], + "copyright": [], + "character": [], + "species": [ + "mammal" + ], + "invalid": [], + "meta": [ + "hi_res" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283349, + "flags": { + "pending": false, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "e", + "fav_count": 28, + "sources": [ + "https://twitter.com/Chromapan/status/1715367273177719054?t=SMdmGxWKfWona1j7daiQ3w\u0026s=19", + "https://cdn.discordapp.com/attachments/519974711216832512/1164959335487656072/SingleDeluxeCOM_Ava_F4.png?ex=65451bed\u0026is=6532a6ed\u0026hm=960bfe4e151f21de7c43bc0f85705bfeddeebf6eeeb76c7324c9a7f91a834436\u0026" + ], + "pools": [], + "relationships": { + "parent_id": 4371046, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 396514, + "description": "", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371047, + "created_at": "2023-10-24T08:23:10.977-04:00", + "updated_at": "2023-10-24T09:40:37.948-04:00", + "file": { + "width": 2000, + "height": 1290, + "ext": "png", + "size": 3499055, + "md5": "f14b1c6a15140ece374cb3d2dc82e3c7", + "url": "https://static1.e621.net/data/f1/4b/f14b1c6a15140ece374cb3d2dc82e3c7.png" + }, + "preview": { + "width": 150, + "height": 96, + "url": "https://static1.e621.net/data/preview/f1/4b/f14b1c6a15140ece374cb3d2dc82e3c7.jpg" + }, + "sample": { + "has": true, + "height": 548, + "width": 850, + "url": "https://static1.e621.net/data/sample/f1/4b/f14b1c6a15140ece374cb3d2dc82e3c7.jpg", + "alternates": {} + }, + "score": { + "up": 7, + "down": 0, + "total": 7 + }, + "tags": { + "general": [ + "anthro", + "areola", + "bodily_fluids", + "clothed", + "clothing", + "gynomorph", + "hair", + "hair_over_eye", + "intersex", + "lactating", + "legwear", + "nipples", + "one_eye_obstructed", + "overweight", + "overweight_gynomorph", + "overweight_intersex", + "panties", + "sweat", + "thick_thighs", + "thigh_highs", + "topless", + "underwear" + ], + "artist": [ + "chromapan" + ], + "copyright": [], + "character": [], + "species": [ + "mammal" + ], + "invalid": [], + "meta": [ + "hi_res" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283344, + "flags": { + "pending": false, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "e", + "fav_count": 12, + "sources": [ + "https://twitter.com/Chromapan/status/1715367273177719054?t=SMdmGxWKfWona1j7daiQ3w\u0026s=19", + "https://cdn.discordapp.com/attachments/519974711216832512/1164959334367756429/SingleDeluxeCOM_Ava_F2.png?ex=65451bed\u0026is=6532a6ed\u0026hm=7dcd08321e7f42e2f2ae425268dfac4d35ef7704161ba9550500fd8694b02fec\u0026" + ], + "pools": [], + "relationships": { + "parent_id": 4371046, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 396514, + "description": "", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371046, + "created_at": "2023-10-24T08:22:58.328-04:00", + "updated_at": "2023-10-24T09:40:21.375-04:00", + "file": { + "width": 2000, + "height": 1290, + "ext": "png", + "size": 3472848, + "md5": "f01fd8dd2fc46197337bd9266af7b3f5", + "url": "https://static1.e621.net/data/f0/1f/f01fd8dd2fc46197337bd9266af7b3f5.png" + }, + "preview": { + "width": 150, + "height": 96, + "url": "https://static1.e621.net/data/preview/f0/1f/f01fd8dd2fc46197337bd9266af7b3f5.jpg" + }, + "sample": { + "has": true, + "height": 548, + "width": 850, + "url": "https://static1.e621.net/data/sample/f0/1f/f01fd8dd2fc46197337bd9266af7b3f5.jpg", + "alternates": {} + }, + "score": { + "up": 8, + "down": 0, + "total": 8 + }, + "tags": { + "general": [ + "anthro", + "areola", + "big_breasts", + "bodily_fluids", + "breasts", + "clothed", + "clothing", + "gynomorph", + "hair", + "hair_over_eye", + "huge_breasts", + "intersex", + "legwear", + "nipples", + "one_eye_obstructed", + "overweight", + "overweight_gynomorph", + "overweight_intersex", + "panties", + "sweat", + "thick_thighs", + "thigh_highs", + "topless", + "underwear" + ], + "artist": [ + "chromapan" + ], + "copyright": [], + "character": [], + "species": [ + "mammal" + ], + "invalid": [], + "meta": [ + "hi_res" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283357, + "flags": { + "pending": false, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "e", + "fav_count": 15, + "sources": [ + "https://twitter.com/Chromapan/status/1715367273177719054?t=SMdmGxWKfWona1j7daiQ3w\u0026s=19", + "https://cdn.discordapp.com/attachments/519974711216832512/1164959333436641402/SingleDeluxeCOM_Ava_F1.png?ex=65451bec\u0026is=6532a6ec\u0026hm=25203c6f501da89d7ba9ce3335776d5c8ef149b81b0cc4616d76d0ac3f2facef\u0026" + ], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": true, + "has_active_children": true, + "children": [ + 4371047, + 4371048 + ] + }, + "approver_id": null, + "uploader_id": 396514, + "description": "", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371045, + "created_at": "2023-10-24T08:22:48.321-04:00", + "updated_at": "2023-10-24T09:40:03.849-04:00", + "file": { + "width": 1600, + "height": 2000, + "ext": "png", + "size": 4268368, + "md5": "e31dfe55650c52bf52329efb18be0243", + "url": "https://static1.e621.net/data/e3/1d/e31dfe55650c52bf52329efb18be0243.png" + }, + "preview": { + "width": 120, + "height": 150, + "url": "https://static1.e621.net/data/preview/e3/1d/e31dfe55650c52bf52329efb18be0243.jpg" + }, + "sample": { + "has": true, + "height": 1062, + "width": 850, + "url": "https://static1.e621.net/data/sample/e3/1d/e31dfe55650c52bf52329efb18be0243.jpg", + "alternates": {} + }, + "score": { + "up": 10, + "down": 0, + "total": 10 + }, + "tags": { + "general": [ + "anal", + "anal_penetration", + "anthro", + "anthro_on_anthro", + "anthro_penetrated", + "anthro_penetrating", + "anthro_penetrating_anthro", + "balls", + "big_balls", + "big_breasts", + "blue_background", + "bodily_fluids", + "bouncing_balls", + "breast_size_difference", + "breasts", + "cum", + "cum_in_ass", + "cum_inflation", + "cum_inside", + "genital_fluids", + "genitals", + "gynomorph", + "gynomorph/gynomorph", + "gynomorph_penetrated", + "gynomorph_penetrating", + "gynomorph_penetrating_gynomorph", + "gynomorph_penetrating_intersex", + "huge_breasts", + "inflation", + "intersex", + "intersex/intersex", + "intersex_penetrated", + "intersex_penetrating", + "intersex_penetrating_gynomorph", + "intersex_penetrating_intersex", + "larger_gynomorph", + "larger_intersex", + "nipples", + "penetration", + "penile", + "penile_penetration", + "penis", + "penis_in_ass", + "penis_size_difference", + "sex", + "simple_background", + "size_difference", + "smaller_gynomorph", + "smaller_intersex", + "smaller_penetrated" + ], + "artist": [ + "chromapan" + ], + "copyright": [], + "character": [], + "species": [ + "fish", + "lagomorph", + "leporid", + "mammal", + "marine", + "rabbit", + "shark" + ], + "invalid": [], + "meta": [ + "hi_res" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283340, + "flags": { + "pending": false, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "e", + "fav_count": 34, + "sources": [ + "https://x.com/Chromapan/status/1715004877091578308?s=20", + "https://cdn.discordapp.com/attachments/519974711216832512/1164567325220016169/DoubleDeluxeCOM_Sendra_F3.png?ex=6543aed6\u0026is=653139d6\u0026hm=eab343f6a28d3bfbac47bb1a8d8817e35895b743dd9ec746440c254bc6f11163\u0026" + ], + "pools": [], + "relationships": { + "parent_id": 4371043, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 396514, + "description": "", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371044, + "created_at": "2023-10-24T08:22:39.234-04:00", + "updated_at": "2023-10-24T09:39:52.747-04:00", + "file": { + "width": 1600, + "height": 2000, + "ext": "png", + "size": 4209717, + "md5": "ecedf0b816ff47e22eafc66c6033207f", + "url": "https://static1.e621.net/data/ec/ed/ecedf0b816ff47e22eafc66c6033207f.png" + }, + "preview": { + "width": 120, + "height": 150, + "url": "https://static1.e621.net/data/preview/ec/ed/ecedf0b816ff47e22eafc66c6033207f.jpg" + }, + "sample": { + "has": true, + "height": 1062, + "width": 850, + "url": "https://static1.e621.net/data/sample/ec/ed/ecedf0b816ff47e22eafc66c6033207f.jpg", + "alternates": {} + }, + "score": { + "up": 6, + "down": 0, + "total": 6 + }, + "tags": { + "general": [ + "anal", + "anal_penetration", + "anthro", + "anthro_on_anthro", + "anthro_penetrated", + "anthro_penetrating", + "anthro_penetrating_anthro", + "balls", + "big_balls", + "big_breasts", + "blue_background", + "bouncing_balls", + "breast_size_difference", + "breasts", + "genitals", + "gynomorph", + "gynomorph/gynomorph", + "gynomorph_penetrated", + "gynomorph_penetrating", + "gynomorph_penetrating_gynomorph", + "gynomorph_penetrating_intersex", + "huge_breasts", + "intersex", + "intersex/intersex", + "intersex_penetrated", + "intersex_penetrating", + "intersex_penetrating_gynomorph", + "intersex_penetrating_intersex", + "larger_gynomorph", + "larger_intersex", + "nipples", + "penetration", + "penile", + "penile_penetration", + "penis", + "penis_in_ass", + "penis_size_difference", + "sex", + "simple_background", + "size_difference", + "smaller_gynomorph", + "smaller_intersex", + "smaller_penetrated" + ], + "artist": [ + "chromapan" + ], + "copyright": [], + "character": [], + "species": [ + "fish", + "lagomorph", + "leporid", + "mammal", + "marine", + "rabbit", + "shark" + ], + "invalid": [], + "meta": [ + "hi_res" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283338, + "flags": { + "pending": false, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "e", + "fav_count": 17, + "sources": [ + "https://x.com/Chromapan/status/1715004877091578308?s=20", + "https://cdn.discordapp.com/attachments/519974711216832512/1164567324255330394/DoubleDeluxeCOM_Sendra_F2.png?ex=6543aed6\u0026is=653139d6\u0026hm=55d9e97db7054f457b0a86fdb4d157421e8be8e114ca70270afbe84c9a92042e\u0026" + ], + "pools": [], + "relationships": { + "parent_id": 4371043, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 396514, + "description": "", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371043, + "created_at": "2023-10-24T08:22:28.604-04:00", + "updated_at": "2023-10-24T09:39:35.685-04:00", + "file": { + "width": 1600, + "height": 2000, + "ext": "png", + "size": 4210685, + "md5": "1e3e75ccccc3a4ae5a7e0eb2bfe20b99", + "url": "https://static1.e621.net/data/1e/3e/1e3e75ccccc3a4ae5a7e0eb2bfe20b99.png" + }, + "preview": { + "width": 120, + "height": 150, + "url": "https://static1.e621.net/data/preview/1e/3e/1e3e75ccccc3a4ae5a7e0eb2bfe20b99.jpg" + }, + "sample": { + "has": true, + "height": 1062, + "width": 850, + "url": "https://static1.e621.net/data/sample/1e/3e/1e3e75ccccc3a4ae5a7e0eb2bfe20b99.jpg", + "alternates": {} + }, + "score": { + "up": 11, + "down": 0, + "total": 11 + }, + "tags": { + "general": [ + "anal", + "anal_penetration", + "anthro", + "anthro_on_anthro", + "anthro_penetrated", + "anthro_penetrating", + "anthro_penetrating_anthro", + "balls", + "big_balls", + "big_breasts", + "blue_background", + "bouncing_balls", + "breast_size_difference", + "breasts", + "genitals", + "gynomorph", + "gynomorph/gynomorph", + "gynomorph_penetrated", + "gynomorph_penetrating", + "gynomorph_penetrating_gynomorph", + "gynomorph_penetrating_intersex", + "huge_breasts", + "intersex", + "intersex/intersex", + "intersex_penetrated", + "intersex_penetrating", + "intersex_penetrating_gynomorph", + "intersex_penetrating_intersex", + "larger_gynomorph", + "larger_intersex", + "nipples", + "penetration", + "penile", + "penile_penetration", + "penis", + "penis_in_ass", + "penis_size_difference", + "sex", + "simple_background", + "size_difference", + "smaller_gynomorph", + "smaller_intersex", + "smaller_penetrated" + ], + "artist": [ + "chromapan" + ], + "copyright": [], + "character": [], + "species": [ + "fish", + "lagomorph", + "leporid", + "mammal", + "marine", + "rabbit", + "shark" + ], + "invalid": [], + "meta": [ + "hi_res" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283339, + "flags": { + "pending": false, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "e", + "fav_count": 28, + "sources": [ + "https://x.com/Chromapan/status/1715004877091578308?s=20", + "https://cdn.discordapp.com/attachments/519974711216832512/1164567322925748274/DoubleDeluxeCOM_Sendra_F1.png?ex=6543aed6\u0026is=653139d6\u0026hm=6deaf8fa4970aa31613ff890d4bc3334b04d1327d3322bd2ab472eceb45c6b88\u0026" + ], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": true, + "has_active_children": true, + "children": [ + 4371044, + 4371045 + ] + }, + "approver_id": null, + "uploader_id": 396514, + "description": "", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371042, + "created_at": "2023-10-24T08:22:26.125-04:00", + "updated_at": "2023-10-24T09:40:45.266-04:00", + "file": { + "width": 5142, + "height": 3228, + "ext": "png", + "size": 13766468, + "md5": "795b16e01f8e1f8b7a98926ea3bf7f44", + "url": "https://static1.e621.net/data/79/5b/795b16e01f8e1f8b7a98926ea3bf7f44.png" + }, + "preview": { + "width": 150, + "height": 94, + "url": "https://static1.e621.net/data/preview/79/5b/795b16e01f8e1f8b7a98926ea3bf7f44.jpg" + }, + "sample": { + "has": true, + "height": 533, + "width": 850, + "url": "https://static1.e621.net/data/sample/79/5b/795b16e01f8e1f8b7a98926ea3bf7f44.jpg", + "alternates": {} + }, + "score": { + "up": 16, + "down": 0, + "total": 16 + }, + "tags": { + "general": [ + "2_penises", + "animal_genitalia", + "animal_penis", + "anus", + "big_penis", + "bite", + "bodily_fluids", + "breeding_mount", + "claws", + "cum", + "ejaculation", + "erection", + "feral", + "from_behind_position", + "fur", + "genital_fluids", + "genitals", + "hemipenes", + "horn", + "machine", + "male", + "male_penetrating", + "membrane_(anatomy)", + "membranous_wings", + "mounting", + "multi_genitalia", + "multi_penis", + "neck_bite", + "onomatopoeia", + "penetration", + "penile", + "penile_spines", + "penis", + "pink_penis", + "red_body", + "red_fur", + "red_scales", + "saliva", + "scales", + "sex", + "sex_toy", + "solo", + "sound_effects", + "tail", + "text", + "wings" + ], + "artist": [ + "naive_tabby" + ], + "copyright": [], + "character": [ + "entropy_(billeur)" + ], + "species": [ + "dragon", + "scalie", + "western_dragon" + ], + "invalid": [], + "meta": [ + "absurd_res", + "digital_media_(artwork)", + "hi_res" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283335, + "flags": { + "pending": true, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "e", + "fav_count": 46, + "sources": [ + "https://www.furaffinity.net/view/54157357/" + ], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 1124811, + "description": "", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371041, + "created_at": "2023-10-24T08:22:17.798-04:00", + "updated_at": "2023-10-24T09:36:38.789-04:00", + "file": { + "width": 1620, + "height": 2000, + "ext": "png", + "size": 2849563, + "md5": "f65073797c8930a8b77800fdf5a811ab", + "url": "https://static1.e621.net/data/f6/50/f65073797c8930a8b77800fdf5a811ab.png" + }, + "preview": { + "width": 121, + "height": 150, + "url": "https://static1.e621.net/data/preview/f6/50/f65073797c8930a8b77800fdf5a811ab.jpg" + }, + "sample": { + "has": true, + "height": 1049, + "width": 850, + "url": "https://static1.e621.net/data/sample/f6/50/f65073797c8930a8b77800fdf5a811ab.jpg", + "alternates": {} + }, + "score": { + "up": 10, + "down": -1, + "total": 9 + }, + "tags": { + "general": [ + "big_breasts", + "breasts", + "broom", + "broom_riding", + "cleaning_tool", + "clothing", + "condom", + "condom_decoration", + "female", + "filled_condom", + "hat", + "headgear", + "headwear", + "huge_breasts", + "hyper", + "hyper_breasts", + "legwear", + "licking", + "navel", + "panties", + "sexual_barrier_device", + "thick_thighs", + "thigh_highs", + "tongue", + "tongue_out", + "underwear", + "witch_hat" + ], + "artist": [ + "chromapan" + ], + "copyright": [], + "character": [ + "katherine_(appledees)" + ], + "species": [ + "mammal" + ], + "invalid": [], + "meta": [ + "hi_res" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283511, + "flags": { + "pending": false, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "e", + "fav_count": 23, + "sources": [ + "https://twitter.com/Chromapan/status/1714648244666368005", + "https://cdn.discordapp.com/attachments/519974711216832512/1164207340275376158/SingleDeluxeCOM_Dee_F2.png?ex=65425f93\u0026is=652fea93\u0026hm=fbe8345fbc303f994ca2f35a867a3297093ced245713c75e53b085c377a1681d\u0026" + ], + "pools": [], + "relationships": { + "parent_id": 4371039, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 396514, + "description": "", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371040, + "created_at": "2023-10-24T08:22:17.503-04:00", + "updated_at": "2023-10-24T09:27:49.231-04:00", + "file": { + "width": 1620, + "height": 2000, + "ext": "png", + "size": 3400136, + "md5": "ee3ccc9fb95c3dc78724dec368b7a2bb", + "url": "https://static1.e621.net/data/ee/3c/ee3ccc9fb95c3dc78724dec368b7a2bb.png" + }, + "preview": { + "width": 121, + "height": 150, + "url": "https://static1.e621.net/data/preview/ee/3c/ee3ccc9fb95c3dc78724dec368b7a2bb.jpg" + }, + "sample": { + "has": true, + "height": 1049, + "width": 850, + "url": "https://static1.e621.net/data/sample/ee/3c/ee3ccc9fb95c3dc78724dec368b7a2bb.jpg", + "alternates": {} + }, + "score": { + "up": 4, + "down": 0, + "total": 4 + }, + "tags": { + "general": [ + "big_breasts", + "bodily_fluids", + "breasts", + "broom", + "broom_riding", + "cleaning_tool", + "clothed", + "clothing", + "condom", + "condom_decoration", + "cum", + "cum_on_breasts", + "female", + "filled_condom", + "genital_fluids", + "hat", + "headgear", + "headwear", + "huge_breasts", + "hyper", + "hyper_breasts", + "legwear", + "licking", + "navel", + "panties", + "sexual_barrier_device", + "thick_thighs", + "thigh_highs", + "tongue", + "tongue_out", + "topless", + "underwear", + "witch_hat" + ], + "artist": [ + "chromapan" + ], + "copyright": [], + "character": [ + "katherine_(appledees)" + ], + "species": [ + "mammal" + ], + "invalid": [], + "meta": [ + "hi_res" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283509, + "flags": { + "pending": false, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "e", + "fav_count": 14, + "sources": [ + "https://twitter.com/Chromapan/status/1714648244666368005", + "https://cdn.discordapp.com/attachments/519974711216832512/1164207341131018383/SingleDeluxeCOM_Dee_F3.png?ex=65425f94\u0026is=652fea94\u0026hm=d24f20e8ba27b3fe70e10456c8975dc665cf5382d15b1dc16ad64a4c05c2ac53\u0026" + ], + "pools": [], + "relationships": { + "parent_id": 4371039, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 396514, + "description": "", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371039, + "created_at": "2023-10-24T08:21:47.238-04:00", + "updated_at": "2023-10-24T09:30:36.533-04:00", + "file": { + "width": 1620, + "height": 2000, + "ext": "png", + "size": 2836258, + "md5": "41348c50e65c2ea8ab32920b95e6bbf0", + "url": "https://static1.e621.net/data/41/34/41348c50e65c2ea8ab32920b95e6bbf0.png" + }, + "preview": { + "width": 121, + "height": 150, + "url": "https://static1.e621.net/data/preview/41/34/41348c50e65c2ea8ab32920b95e6bbf0.jpg" + }, + "sample": { + "has": true, + "height": 1049, + "width": 850, + "url": "https://static1.e621.net/data/sample/41/34/41348c50e65c2ea8ab32920b95e6bbf0.jpg", + "alternates": {} + }, + "score": { + "up": 8, + "down": 0, + "total": 8 + }, + "tags": { + "general": [ + "big_breasts", + "breasts", + "broom", + "broom_riding", + "cleaning_tool", + "clothing", + "condom", + "condom_decoration", + "female", + "filled_condom", + "hat", + "headgear", + "headwear", + "huge_breasts", + "legwear", + "licking", + "navel", + "sexual_barrier_device", + "thick_thighs", + "thigh_highs", + "tongue", + "tongue_out", + "witch_hat" + ], + "artist": [ + "chromapan" + ], + "copyright": [], + "character": [ + "katherine_(appledees)" + ], + "species": [ + "mammal" + ], + "invalid": [], + "meta": [ + "hi_res" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283506, + "flags": { + "pending": false, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "e", + "fav_count": 9, + "sources": [ + "https://twitter.com/Chromapan/status/1714648244666368005", + "https://cdn.discordapp.com/attachments/519974711216832512/1164207335556776027/SingleDeluxeCOM_Dee_F1.png?ex=65425f92\u0026is=652fea92\u0026hm=792c18b2645edf3a88d02c583b80194af0e54553ee420213b6f6ae1cf1f2eb29\u0026" + ], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": true, + "has_active_children": true, + "children": [ + 4371040, + 4371041 + ] + }, + "approver_id": null, + "uploader_id": 396514, + "description": "", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371038, + "created_at": "2023-10-24T08:21:43.864-04:00", + "updated_at": "2023-10-24T09:36:47.604-04:00", + "file": { + "width": 1220, + "height": 1600, + "ext": "png", + "size": 1139272, + "md5": "b4df5cfee3cac83732c71ad1fc55137d", + "url": "https://static1.e621.net/data/b4/df/b4df5cfee3cac83732c71ad1fc55137d.png" + }, + "preview": { + "width": 114, + "height": 150, + "url": "https://static1.e621.net/data/preview/b4/df/b4df5cfee3cac83732c71ad1fc55137d.jpg" + }, + "sample": { + "has": true, + "height": 1114, + "width": 850, + "url": "https://static1.e621.net/data/sample/b4/df/b4df5cfee3cac83732c71ad1fc55137d.jpg", + "alternates": {} + }, + "score": { + "up": 6, + "down": 0, + "total": 6 + }, + "tags": { + "general": [ + "anthro", + "anthro_on_anthro", + "big_breasts", + "bodily_fluids", + "breast_play", + "breast_suck", + "breasts", + "cleavage", + "clothed", + "clothing", + "cradle_position", + "cradling", + "female", + "genital_fluids", + "genitals", + "handjob", + "holding_penis", + "huge_breasts", + "larger_female", + "male", + "male/female", + "nursing_handjob", + "penile", + "penis", + "precum", + "sex", + "size_difference", + "smaller_male", + "sucking", + "teasing", + "thick_thighs" + ], + "artist": [ + "chromapan" + ], + "copyright": [], + "character": [], + "species": [ + "bovid", + "caprine", + "mammal" + ], + "invalid": [], + "meta": [ + "hi_res" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283470, + "flags": { + "pending": false, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "e", + "fav_count": 23, + "sources": [ + "https://x.com/Chromapan/status/1713585527486583251?s=20", + "https://cdn.discordapp.com/attachments/519974711216832512/1163146740728352808/DoubleSketchCOM_Vet_F1.png?ex=6547be51\u0026is=65354951\u0026hm=220fbc677861f237ab3b704d2b93f5343f18083de55ba0d23b1a112712e21fc4\u0026" + ], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 396514, + "description": "", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371037, + "created_at": "2023-10-24T08:21:40.835-04:00", + "updated_at": "2023-10-24T09:37:42.320-04:00", + "file": { + "width": 1600, + "height": 1050, + "ext": "png", + "size": 830744, + "md5": "583a55c76693c367675663599af60b38", + "url": "https://static1.e621.net/data/58/3a/583a55c76693c367675663599af60b38.png" + }, + "preview": { + "width": 150, + "height": 98, + "url": "https://static1.e621.net/data/preview/58/3a/583a55c76693c367675663599af60b38.jpg" + }, + "sample": { + "has": true, + "height": 557, + "width": 850, + "url": "https://static1.e621.net/data/sample/58/3a/583a55c76693c367675663599af60b38.jpg", + "alternates": {} + }, + "score": { + "up": 5, + "down": 0, + "total": 5 + }, + "tags": { + "general": [ + "69_position", + "anthro", + "anthro_on_anthro", + "big_breasts", + "big_butt", + "bodily_fluids", + "breast_play", + "breasts", + "butt", + "cum", + "cum_on_breasts", + "cunnilingus", + "female", + "genital_fluids", + "genitals", + "male", + "male/female", + "nude", + "oral", + "pussy", + "sex", + "thick_thighs", + "titfuck", + "vaginal" + ], + "artist": [ + "chromapan" + ], + "copyright": [], + "character": [], + "species": [ + "mammal" + ], + "invalid": [], + "meta": [ + "hi_res", + "monochrome" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283576, + "flags": { + "pending": false, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "e", + "fav_count": 18, + "sources": [ + "https://x.com/Chromapan/status/1713319469399843204?s=20", + "https://cdn.discordapp.com/attachments/519974711216832512/1162878417360932884/DoubleSketchCOM_Bunnydew_F2.png?ex=6546c46b\u0026is=65344f6b\u0026hm=a92ad0a5198e3e94db6de561fb6580c6e98ec2b08b5198dfb989bd32977fe39e\u0026" + ], + "pools": [], + "relationships": { + "parent_id": 4371036, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 396514, + "description": "", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371036, + "created_at": "2023-10-24T08:21:26.003-04:00", + "updated_at": "2023-10-24T09:22:00.259-04:00", + "file": { + "width": 1600, + "height": 1050, + "ext": "png", + "size": 795795, + "md5": "fa227ddcef08248aa7b225c376292ef0", + "url": "https://static1.e621.net/data/fa/22/fa227ddcef08248aa7b225c376292ef0.png" + }, + "preview": { + "width": 150, + "height": 98, + "url": "https://static1.e621.net/data/preview/fa/22/fa227ddcef08248aa7b225c376292ef0.jpg" + }, + "sample": { + "has": true, + "height": 557, + "width": 850, + "url": "https://static1.e621.net/data/sample/fa/22/fa227ddcef08248aa7b225c376292ef0.jpg", + "alternates": {} + }, + "score": { + "up": 4, + "down": 0, + "total": 4 + }, + "tags": { + "general": [ + "69_position", + "anthro", + "anthro_on_anthro", + "big_breasts", + "big_butt", + "breast_play", + "breasts", + "butt", + "cunnilingus", + "female", + "foreskin", + "genitals", + "male", + "male/female", + "nude", + "oral", + "penis", + "pussy", + "retracted_foreskin", + "sex", + "thick_thighs", + "titfuck", + "vaginal" + ], + "artist": [ + "chromapan" + ], + "copyright": [], + "character": [], + "species": [ + "mammal" + ], + "invalid": [], + "meta": [ + "hi_res", + "monochrome" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283480, + "flags": { + "pending": false, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "e", + "fav_count": 13, + "sources": [ + "https://x.com/Chromapan/status/1713319469399843204?s=20", + "https://cdn.discordapp.com/attachments/519974711216832512/1162878416861794354/DoubleSketchCOM_Bunnydew_F1.png?ex=6546c46b\u0026is=65344f6b\u0026hm=e85bd56b6add8a768b2e9628bb94e2826466576b79e1c2dedb9b744a1a14995a\u0026" + ], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": true, + "has_active_children": true, + "children": [ + 4371037 + ] + }, + "approver_id": null, + "uploader_id": 396514, + "description": "", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371035, + "created_at": "2023-10-24T08:21:23.175-04:00", + "updated_at": "2023-10-24T09:38:52.384-04:00", + "file": { + "width": 1380, + "height": 3025, + "ext": "png", + "size": 5098994, + "md5": "a87b3e2b3dd9e2c723a15941e5dca5f8", + "url": "https://static1.e621.net/data/a8/7b/a87b3e2b3dd9e2c723a15941e5dca5f8.png" + }, + "preview": { + "width": 68, + "height": 150, + "url": "https://static1.e621.net/data/preview/a8/7b/a87b3e2b3dd9e2c723a15941e5dca5f8.jpg" + }, + "sample": { + "has": true, + "height": 1863, + "width": 850, + "url": "https://static1.e621.net/data/sample/a8/7b/a87b3e2b3dd9e2c723a15941e5dca5f8.jpg", + "alternates": {} + }, + "score": { + "up": 12, + "down": 0, + "total": 12 + }, + "tags": { + "general": [ + "abdominal_bulge", + "anal", + "anal_penetration", + "anthro", + "anthro_on_anthro", + "anthro_penetrated", + "anthro_penetrating", + "anthro_penetrating_anthro", + "ass_up", + "backsack", + "balls", + "big_breasts", + "big_butt", + "bodily_fluids", + "breast_play", + "breast_suck", + "breasts", + "butt", + "cum", + "dialogue", + "genital_fluids", + "genitals", + "girly", + "gynomorph", + "gynomorph/male", + "gynomorph_penetrated", + "huge_breasts", + "intersex", + "intersex/male", + "intersex_penetrated", + "larger_gynomorph", + "larger_intersex", + "licking", + "male", + "male_penetrating", + "male_penetrating_gynomorph", + "male_penetrating_intersex", + "nipples", + "oral", + "penetration", + "penile", + "penile_penetration", + "penis", + "penis_in_ass", + "rimming", + "sex", + "size_difference", + "smaller_male", + "sucking", + "sweat", + "sweaty_butt", + "tongue", + "tongue_out", + "vein", + "veiny_penis" + ], + "artist": [ + "chromapan" + ], + "copyright": [], + "character": [], + "species": [ + "deer", + "mammal", + "new_world_deer", + "reindeer" + ], + "invalid": [], + "meta": [ + "absurd_res", + "comic", + "hi_res" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283324, + "flags": { + "pending": false, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "e", + "fav_count": 28, + "sources": [ + "https://x.com/Chromapan/status/1712105774179324084?s=20", + "https://cdn.discordapp.com/attachments/519974711216832512/1161669160351305758/COMICCOM_Blu_F1.png?ex=65425e36\u0026is=652fe936\u0026hm=dd25ebc674cef0834441ea3b4ac074b89981bb60f1a3fc6e0781b8de6fc20038\u0026" + ], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 396514, + "description": "", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371034, + "created_at": "2023-10-24T08:18:32.021-04:00", + "updated_at": "2023-10-24T09:10:06.444-04:00", + "file": { + "width": 1000, + "height": 1200, + "ext": "png", + "size": 819821, + "md5": "83202dc2c5041e7ddfc3b3f42c70832f", + "url": "https://static1.e621.net/data/83/20/83202dc2c5041e7ddfc3b3f42c70832f.png" + }, + "preview": { + "width": 125, + "height": 150, + "url": "https://static1.e621.net/data/preview/83/20/83202dc2c5041e7ddfc3b3f42c70832f.jpg" + }, + "sample": { + "has": true, + "height": 1020, + "width": 850, + "url": "https://static1.e621.net/data/sample/83/20/83202dc2c5041e7ddfc3b3f42c70832f.jpg", + "alternates": {} + }, + "score": { + "up": 3, + "down": 0, + "total": 3 + }, + "tags": { + "general": [ + "anthro", + "armwear", + "big_breasts", + "blue_eyes", + "breasts", + "brown_body", + "brown_fur", + "bulge", + "clothing", + "colored_nails", + "fishnet", + "fishnet_armwear", + "fur", + "gynomorph", + "hair", + "intersex", + "lace", + "legwear", + "multicolored_hair", + "nails", + "pose", + "solo", + "solo_focus", + "tail", + "thigh_highs", + "translucent", + "translucent_clothing" + ], + "artist": [ + "cire" + ], + "copyright": [], + "character": [], + "species": [ + "canid", + "canine", + "fox", + "mammal" + ], + "invalid": [], + "meta": [ + "hi_res" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283323, + "flags": { + "pending": true, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "e", + "fav_count": 8, + "sources": [ + "https://www.furaffinity.net/view/54169074/" + ], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 913208, + "description": "", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371033, + "created_at": "2023-10-24T08:18:30.636-04:00", + "updated_at": "2023-10-24T09:14:05.079-04:00", + "file": { + "width": 813, + "height": 983, + "ext": "jpg", + "size": 86258, + "md5": "191f752c2a4a39dc904a8c067227be1f", + "url": "https://static1.e621.net/data/19/1f/191f752c2a4a39dc904a8c067227be1f.jpg" + }, + "preview": { + "width": 124, + "height": 150, + "url": "https://static1.e621.net/data/preview/19/1f/191f752c2a4a39dc904a8c067227be1f.jpg" + }, + "sample": { + "has": false, + "height": 983, + "width": 813, + "url": "https://static1.e621.net/data/19/1f/191f752c2a4a39dc904a8c067227be1f.jpg", + "alternates": {} + }, + "score": { + "up": 7, + "down": 0, + "total": 7 + }, + "tags": { + "general": [ + "anthro", + "bdsm", + "blush", + "bondage", + "bottomwear", + "bound", + "breasts", + "clothing", + "collar", + "cutoffs", + "denim", + "denim_clothing", + "dominant", + "dominant_female", + "duo", + "female", + "female/female", + "fishnet", + "fluffy", + "fluffy_tail", + "leash", + "shorts", + "small_breasts", + "tail" + ], + "artist": [ + "maiiyumi" + ], + "copyright": [ + "nintendo", + "pokemon" + ], + "character": [ + "j0lt" + ], + "species": [ + "fish", + "generation_4_pokemon", + "lucario", + "marine", + "pokemon_(species)", + "shark" + ], + "invalid": [], + "meta": [], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283322, + "flags": { + "pending": true, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "e", + "fav_count": 9, + "sources": [], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 1287013, + "description": "A shark found out what J0LT likes \u003ew\u003c", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371032, + "created_at": "2023-10-24T08:17:48.957-04:00", + "updated_at": "2023-10-24T09:11:51.335-04:00", + "file": { + "width": 3850, + "height": 3781, + "ext": "png", + "size": 3201799, + "md5": "19ca0ef43daca60bf60a13d07ba7ddae", + "url": "https://static1.e621.net/data/19/ca/19ca0ef43daca60bf60a13d07ba7ddae.png" + }, + "preview": { + "width": 150, + "height": 147, + "url": "https://static1.e621.net/data/preview/19/ca/19ca0ef43daca60bf60a13d07ba7ddae.jpg" + }, + "sample": { + "has": true, + "height": 834, + "width": 850, + "url": "https://static1.e621.net/data/sample/19/ca/19ca0ef43daca60bf60a13d07ba7ddae.jpg", + "alternates": {} + }, + "score": { + "up": 4, + "down": 0, + "total": 4 + }, + "tags": { + "general": [ + "areola", + "braided_hair", + "breasts", + "brown_eyes", + "eyelashes", + "female", + "green_body", + "green_skin", + "grey_background", + "hair", + "humanoid_pointy_ears", + "magic_user", + "melee_weapon", + "navel", + "necromancer", + "nipples", + "nude", + "pink_areola", + "pink_nipples", + "pink_sclera", + "polearm", + "purple_hair", + "scythe", + "simple_background", + "small_breasts", + "solo", + "text", + "thick_thighs", + "translucent", + "translucent_body", + "visible_bone", + "weapon", + "wide_hips" + ], + "artist": [ + "critterstew" + ], + "copyright": [], + "character": [], + "species": [ + "goblin", + "humanoid" + ], + "invalid": [], + "meta": [ + "absurd_res", + "hi_res" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283317, + "flags": { + "pending": true, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "e", + "fav_count": 3, + "sources": [ + "https://www.newgrounds.com/art/view/critterstew/gobtober-day-7-necromancer" + ], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 1006937, + "description": "Gobtober day 7 - Necromancer", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371031, + "created_at": "2023-10-24T08:15:21.515-04:00", + "updated_at": "2023-10-24T09:10:33.826-04:00", + "file": { + "width": 1198, + "height": 2048, + "ext": "jpg", + "size": 298120, + "md5": "b99a8b4f1d569ae877d366c3b92d1682", + "url": "https://static1.e621.net/data/b9/9a/b99a8b4f1d569ae877d366c3b92d1682.jpg" + }, + "preview": { + "width": 87, + "height": 150, + "url": "https://static1.e621.net/data/preview/b9/9a/b99a8b4f1d569ae877d366c3b92d1682.jpg" + }, + "sample": { + "has": true, + "height": 1453, + "width": 850, + "url": "https://static1.e621.net/data/sample/b9/9a/b99a8b4f1d569ae877d366c3b92d1682.jpg", + "alternates": {} + }, + "score": { + "up": 2, + "down": 0, + "total": 2 + }, + "tags": { + "general": [ + "big_feet", + "bow_tie", + "butt", + "clothing", + "dominant", + "dominant_male", + "duo", + "feet", + "female", + "foot_fetish", + "foot_focus", + "foot_lick", + "foot_play", + "genitals", + "glowing", + "glowing_eyes", + "hypnosis", + "licking", + "male", + "male/female", + "masturbation", + "mind_control", + "nude", + "penis", + "scar", + "sex", + "smile", + "submissive", + "submissive_female", + "suit", + "text", + "tongue", + "tongue_out" + ], + "artist": [ + "kimberco" + ], + "copyright": [ + "hazbin_hotel" + ], + "character": [ + "alastor_(hazbin_hotel)", + "charlie_morningstar" + ], + "species": [ + "demon", + "humanoid" + ], + "invalid": [], + "meta": [ + "english_text", + "hi_res" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283309, + "flags": { + "pending": true, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "e", + "fav_count": 6, + "sources": [ + "https://twitter.com/KimberCoArts/status/1671199029684842505" + ], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 327342, + "description": "[quote]The mind is a funny thing, isn’t it? Imagine, in a nanosecond, planting ideas in someone’s head, without them even knowing. Ideas that they come to think of as their own. \r\nAllow Chalastor to demonstrate~[/quote]", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371030, + "created_at": "2023-10-24T08:13:48.861-04:00", + "updated_at": "2023-10-24T09:07:03.901-04:00", + "file": { + "width": 4599, + "height": 3508, + "ext": "png", + "size": 2155376, + "md5": "da9430f44358c9e1d9cb051bac9b770c", + "url": "https://static1.e621.net/data/da/94/da9430f44358c9e1d9cb051bac9b770c.png" + }, + "preview": { + "width": 150, + "height": 114, + "url": "https://static1.e621.net/data/preview/da/94/da9430f44358c9e1d9cb051bac9b770c.jpg" + }, + "sample": { + "has": true, + "height": 648, + "width": 850, + "url": "https://static1.e621.net/data/sample/da/94/da9430f44358c9e1d9cb051bac9b770c.jpg", + "alternates": {} + }, + "score": { + "up": 3, + "down": 0, + "total": 3 + }, + "tags": { + "general": [ + "armor", + "big_breasts", + "big_ears", + "blue_hair", + "bottomwear", + "breasts", + "brown_clothing", + "brown_footwear", + "brown_gloves", + "brown_handwear", + "cleavage", + "clothed", + "clothing", + "female", + "footwear", + "gloves", + "green_body", + "green_eyes", + "green_skin", + "grey_background", + "hair", + "handwear", + "headgear", + "helmet", + "holding_object", + "holding_sword", + "holding_weapon", + "humanoid_pointy_ears", + "knight", + "lips", + "melee_weapon", + "plume", + "red_bottomwear", + "red_clothing", + "red_lips", + "red_topwear", + "scar", + "simple_background", + "skindentation", + "solo", + "sword", + "text", + "thick_thighs", + "topwear", + "warrior", + "weapon", + "wide_hips" + ], + "artist": [ + "critterstew" + ], + "copyright": [], + "character": [], + "species": [ + "goblin", + "humanoid" + ], + "invalid": [], + "meta": [ + "absurd_res", + "hi_res" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283305, + "flags": { + "pending": true, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "s", + "fav_count": 4, + "sources": [ + "https://www.newgrounds.com/art/view/critterstew/day-6-of-gobtober-knight" + ], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 1006937, + "description": "Day 6 of Gobtober - Knight!", + "comment_count": 1, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371029, + "created_at": "2023-10-24T08:13:11.885-04:00", + "updated_at": "2023-10-24T09:26:19.092-04:00", + "file": { + "width": 2175, + "height": 1392, + "ext": "png", + "size": 3249259, + "md5": "35042a0b4365c0b211f5fd940b63edec", + "url": "https://static1.e621.net/data/35/04/35042a0b4365c0b211f5fd940b63edec.png" + }, + "preview": { + "width": 150, + "height": 96, + "url": "https://static1.e621.net/data/preview/35/04/35042a0b4365c0b211f5fd940b63edec.jpg" + }, + "sample": { + "has": true, + "height": 544, + "width": 850, + "url": "https://static1.e621.net/data/sample/35/04/35042a0b4365c0b211f5fd940b63edec.jpg", + "alternates": {} + }, + "score": { + "up": 6, + "down": 0, + "total": 6 + }, + "tags": { + "general": [ + "anthro", + "armor", + "big_breasts", + "boots", + "breasts", + "cape", + "clothing", + "facial_markings", + "female", + "footwear", + "garter_straps", + "gauntlets", + "gloves", + "greaves", + "hair", + "handwear", + "head_markings", + "high_heels", + "highlights_(coloring)", + "huge_breasts", + "legwear", + "leotard", + "markings", + "mask", + "melee_weapon", + "muscular", + "muscular_female", + "pauldron", + "purple_eyes", + "solo", + "superhero", + "sword", + "tear_(marking)", + "thigh_boots", + "thigh_highs", + "tricolor", + "weapon", + "white_hair" + ], + "artist": [ + "mastergodai" + ], + "copyright": [ + "famwammer" + ], + "character": [ + "chroma_(famwammer)" + ], + "species": [ + "accipitrid", + "accipitriform", + "avian", + "bald_eagle", + "bird", + "eagle", + "embody_(transformation)", + "sea_eagle" + ], + "invalid": [], + "meta": [ + "hi_res" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283304, + "flags": { + "pending": true, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "s", + "fav_count": 9, + "sources": [ + "https://www.furaffinity.net/view/54169019/" + ], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 629889, + "description": "Eibon talks on holophone\r\n\"Well, my plans fell through. Otto can't lead anymore. She's dropped out to raise some kids... Grimm doesn't WANT to. She only joined the program to prove herself.\"\r\n\r\nChroma sits at a neighboring table, watching the results of her election in real time, expecting to win a second term.\r\n\"San Verde. Baesis. New Kowloon... C'mon!\"\r\n\r\nUmber\r\n\"Quit stressing. You've got this nailed.\"\r\n\r\nAsh\r\n\"Yeah, you've got this by the ass!\"\r\n\r\nEibon\r\n\"Gally? Hardly. A good agent, but her work ethic sucks on toast. Elpis? (Shudder) No thank you.\"\r\n\r\nChroma\r\n\"Mom, you're stressing me out here! Sit down and shut off the phone!\"\r\n\r\nEibon\r\n\"Fuck! Adramalech dropped out!? I've only got a few months left, Goddammit! That just leaves Faith! She's just me with feathers and fluent Spanish. And at that point, why even bother with a successor?\"\r\n\r\nAsh\r\n\"You gotta be fucking kidding...\"\r\n\r\nUmber\r\n\"That's... What is happening?\"\r\n\r\nEibon\r\n\"Are there any left? Any at all? A vote of no confidence!? No confidence in what!? A coward!? They're calling me a coward!? I've been planning this retirement for years! I made it public ages ago! I can't help the timing of it. The deadline was set in stone, and now I need a-\"\r\n\r\nChroma\r\n\"............Mom.... I'll do it...\"\r\n\r\nEibon\r\n\"Hang on, hun. It got real quiet here. What'd you.... Say?\"\r\n\r\nEibon turns to see Chroma with tears in her eyes. She turns up to the holo screen to see Chroma has lost her bid for reelection and won't be president of Neo America for a second term.\"\r\n\r\nChroma\r\n\"I'll do it.\"\r\n\r\nEibon\r\n\"What the fuck? How the hell did you lose!? You were a shoe-in! Everyone said your second term was gonna be a slam dunk!\"\r\n\r\nUmber and Ash are silent, and Chroma is wiping her eyes, knowing she will need to vacate the Ivory Temple in three months.\r\n\r\nChroma\r\n\"It's okay mom... I'll lead Watchtower.\"\r\n\r\n\r\n\r\nArt by @Mastergodai\r\n\r\nCharacter belongs to Famwammer and is part of Fireball.", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371028, + "created_at": "2023-10-24T08:12:52.199-04:00", + "updated_at": "2023-10-24T09:38:16.664-04:00", + "file": { + "width": 2351, + "height": 1567, + "ext": "png", + "size": 3126523, + "md5": "9a0ca2a082fdda6959803d21eb40ded6", + "url": "https://static1.e621.net/data/9a/0c/9a0ca2a082fdda6959803d21eb40ded6.png" + }, + "preview": { + "width": 150, + "height": 99, + "url": "https://static1.e621.net/data/preview/9a/0c/9a0ca2a082fdda6959803d21eb40ded6.jpg" + }, + "sample": { + "has": true, + "height": 566, + "width": 850, + "url": "https://static1.e621.net/data/sample/9a/0c/9a0ca2a082fdda6959803d21eb40ded6.jpg", + "alternates": {} + }, + "score": { + "up": 1, + "down": 0, + "total": 1 + }, + "tags": { + "general": [ + "ambiguous_gender", + "bdsm", + "bondage", + "bound", + "captured", + "coffin", + "feral", + "muffled_speech", + "mummification", + "mummy_wrappings", + "solo", + "struggling", + "wrapped" + ], + "artist": [ + "charly-sparks" + ], + "copyright": [ + "bandai_namco", + "digimon" + ], + "character": [], + "species": [ + "calumon", + "digimon_(species)" + ], + "invalid": [], + "meta": [ + "hi_res" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283307, + "flags": { + "pending": true, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "q", + "fav_count": 3, + "sources": [ + "https://www.furaffinity.net/view/53992659/", + "https://d.furaffinity.net/art/charly-sparks/1696938167/1696938167.charly-sparks_calumummy.png" + ], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 1545440, + "description": "", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + }, + { + "id": 4371027, + "created_at": "2023-10-24T08:10:58.174-04:00", + "updated_at": "2023-10-24T09:29:30.411-04:00", + "file": { + "width": 1790, + "height": 1899, + "ext": "png", + "size": 1926411, + "md5": "89d47fac24cd8bf6a7947fefbd869790", + "url": "https://static1.e621.net/data/89/d4/89d47fac24cd8bf6a7947fefbd869790.png" + }, + "preview": { + "width": 141, + "height": 150, + "url": "https://static1.e621.net/data/preview/89/d4/89d47fac24cd8bf6a7947fefbd869790.jpg" + }, + "sample": { + "has": true, + "height": 901, + "width": 850, + "url": "https://static1.e621.net/data/sample/89/d4/89d47fac24cd8bf6a7947fefbd869790.jpg", + "alternates": {} + }, + "score": { + "up": 12, + "down": 0, + "total": 12 + }, + "tags": { + "general": [ + "anthro", + "belly", + "bottomwear", + "breasts", + "brown_eyes", + "brown_hair", + "clothing", + "crossgender", + "female", + "hair", + "legwear", + "pants", + "simple_background", + "smile", + "solo" + ], + "artist": [ + "g4bby" + ], + "copyright": [ + "project_zomboid" + ], + "character": [ + "spiffo" + ], + "species": [ + "mammal", + "procyonid", + "raccoon" + ], + "invalid": [], + "meta": [ + "hi_res" + ], + "lore": [] + }, + "locked_tags": [], + "change_seq": 51283289, + "flags": { + "pending": true, + "flagged": false, + "note_locked": false, + "status_locked": false, + "rating_locked": false, + "deleted": false + }, + "rating": "e", + "fav_count": 23, + "sources": [ + "https://twitter.com/g4bby4ka/status/1714030648527127012/photo/1" + ], + "pools": [], + "relationships": { + "parent_id": null, + "has_children": false, + "has_active_children": false, + "children": [] + }, + "approver_id": null, + "uploader_id": 1680928, + "description": "", + "comment_count": 0, + "is_favorited": false, + "has_notes": false, + "duration": null + } + ] +} \ No newline at end of file diff --git a/tests/tag.json b/tests/tag.json new file mode 100644 index 0000000..5e9df63 --- /dev/null +++ b/tests/tag.json @@ -0,0 +1,11 @@ +{ + "id": 1337, + "name": "clothed", + "post_count": 945399, + "related_tags": "clothed 300 clothing 300 mammal 255 anthro 242 female 200 hi_res 197 solo 154 breasts 153 hair 152 male 140 fur 121 duo 102 genitals 101 digital_media_(artwork) 97 canid 88 topwear 88 bodily_fluids 87 text 86 canine 84 simple_background 82 big_breasts 79 smile 79 open_mouth 76 tail 76 felid 75", + "related_tags_updated_at": "2023-10-04T15:01:19.597-04:00", + "category": 0, + "is_locked": false, + "created_at": "2020-03-05T05:49:37.994-05:00", + "updated_at": "2023-10-04T15:01:19.598-04:00" +} \ No newline at end of file diff --git a/tests/tags.json b/tests/tags.json new file mode 100644 index 0000000..c65c088 --- /dev/null +++ b/tests/tags.json @@ -0,0 +1,827 @@ +[ + { + "id": 1251286, + "name": "ayre_(armored_core)", + "post_count": 1, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T09:29:25.610-04:00", + "category": 4, + "is_locked": false, + "created_at": "2023-10-24T09:29:27.171-04:00", + "updated_at": "2023-10-24T09:29:27.171-04:00" + }, + { + "id": 1251285, + "name": "621_(armored_core)", + "post_count": 1, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T09:29:25.610-04:00", + "category": 4, + "is_locked": false, + "created_at": "2023-10-24T09:29:27.162-04:00", + "updated_at": "2023-10-24T09:29:27.162-04:00" + }, + { + "id": 1251283, + "name": "thatguywithabadattitude", + "post_count": 1, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T09:23:37.329-04:00", + "category": 1, + "is_locked": false, + "created_at": "2023-10-24T09:23:38.656-04:00", + "updated_at": "2023-10-24T09:23:51.716-04:00" + }, + { + "id": 1251282, + "name": "hamantha_(jack_stauber)", + "post_count": 1, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T09:23:37.329-04:00", + "category": 4, + "is_locked": false, + "created_at": "2023-10-24T09:23:38.647-04:00", + "updated_at": "2023-10-24T09:23:44.770-04:00" + }, + { + "id": 1251281, + "name": "artist_rayminonfox", + "post_count": 1, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T09:14:50.934-04:00", + "category": 0, + "is_locked": false, + "created_at": "2023-10-24T09:14:53.598-04:00", + "updated_at": "2023-10-24T09:14:53.598-04:00" + }, + { + "id": 1251280, + "name": "kminsi21", + "post_count": 1, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T09:04:08.935-04:00", + "category": 1, + "is_locked": false, + "created_at": "2023-10-24T09:04:12.868-04:00", + "updated_at": "2023-10-24T09:04:12.868-04:00" + }, + { + "id": 1251279, + "name": "mori_(thetomereborn)", + "post_count": 1, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T08:54:06.887-04:00", + "category": 4, + "is_locked": false, + "created_at": "2023-10-24T08:54:06.910-04:00", + "updated_at": "2023-10-24T08:54:20.520-04:00" + }, + { + "id": 1251278, + "name": "rbxgemini", + "post_count": 1, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T08:26:26.437-04:00", + "category": 1, + "is_locked": false, + "created_at": "2023-10-24T08:26:26.869-04:00", + "updated_at": "2023-10-24T08:28:55.655-04:00" + }, + { + "id": 1251275, + "name": "miss_m", + "post_count": 10, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T07:54:40.845-04:00", + "category": 4, + "is_locked": false, + "created_at": "2023-10-24T07:54:40.872-04:00", + "updated_at": "2023-10-24T07:54:53.333-04:00" + }, + { + "id": 1251274, + "name": "sauna_(vorelover203)", + "post_count": 1, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T07:49:44.945-04:00", + "category": 4, + "is_locked": false, + "created_at": "2023-10-24T07:49:44.975-04:00", + "updated_at": "2023-10-24T07:49:53.215-04:00" + }, + { + "id": 1251273, + "name": "critterstew", + "post_count": 10, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T07:47:01.742-04:00", + "category": 1, + "is_locked": false, + "created_at": "2023-10-24T07:47:04.951-04:00", + "updated_at": "2023-10-24T07:47:04.951-04:00" + }, + { + "id": 1251272, + "name": "margo_(g4bby)", + "post_count": 1, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T07:38:58.883-04:00", + "category": 0, + "is_locked": false, + "created_at": "2023-10-24T07:39:00.096-04:00", + "updated_at": "2023-10-24T07:39:00.096-04:00" + }, + { + "id": 1251271, + "name": "lemon_(lewdchuu)", + "post_count": 6, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T07:32:40.389-04:00", + "category": 4, + "is_locked": false, + "created_at": "2023-10-24T07:32:41.621-04:00", + "updated_at": "2023-10-24T07:32:49.273-04:00" + }, + { + "id": 1251270, + "name": "bg3", + "post_count": 1, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T07:27:49.080-04:00", + "category": 0, + "is_locked": false, + "created_at": "2023-10-24T07:27:50.484-04:00", + "updated_at": "2023-10-24T07:27:50.484-04:00" + }, + { + "id": 1251269, + "name": "tail_in_paw", + "post_count": 1, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T06:41:39.634-04:00", + "category": 0, + "is_locked": false, + "created_at": "2023-10-24T06:41:39.670-04:00", + "updated_at": "2023-10-24T06:41:39.670-04:00" + }, + { + "id": 1251268, + "name": "tail_in_own_paw", + "post_count": 1, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T06:41:39.634-04:00", + "category": 0, + "is_locked": false, + "created_at": "2023-10-24T06:41:39.667-04:00", + "updated_at": "2023-10-24T06:41:39.667-04:00" + }, + { + "id": 1251267, + "name": "tail_in_own_hand", + "post_count": 1, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T06:41:39.634-04:00", + "category": 0, + "is_locked": false, + "created_at": "2023-10-24T06:41:39.656-04:00", + "updated_at": "2023-10-24T06:41:39.656-04:00" + }, + { + "id": 1251265, + "name": "sorrelkit_(warriors)", + "post_count": 1, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T06:38:09.807-04:00", + "category": 0, + "is_locked": false, + "created_at": "2023-10-24T06:38:09.868-04:00", + "updated_at": "2023-10-24T06:38:09.868-04:00" + }, + { + "id": 1251264, + "name": "mr_o_(bebebebebe)", + "post_count": 9, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T06:11:42.600-04:00", + "category": 4, + "is_locked": false, + "created_at": "2023-10-24T06:11:42.629-04:00", + "updated_at": "2023-10-24T09:05:19.594-04:00" + }, + { + "id": 1251263, + "name": "pregnancy_risk_kink", + "post_count": 1, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T06:03:51.859-04:00", + "category": 0, + "is_locked": false, + "created_at": "2023-10-24T06:03:51.883-04:00", + "updated_at": "2023-10-24T06:03:51.883-04:00" + }, + { + "id": 1251262, + "name": "wasp_trooper_(bug_fables)", + "post_count": 3, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T05:53:31.291-04:00", + "category": 4, + "is_locked": false, + "created_at": "2023-10-24T05:53:31.327-04:00", + "updated_at": "2023-10-24T05:53:43.527-04:00" + }, + { + "id": 1251260, + "name": "mammi_wolfgang", + "post_count": 4, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T05:19:10.341-04:00", + "category": 4, + "is_locked": false, + "created_at": "2023-10-24T05:19:11.134-04:00", + "updated_at": "2023-10-24T05:19:40.522-04:00" + }, + { + "id": 1251259, + "name": "flo_wolfgang", + "post_count": 4, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T05:19:10.341-04:00", + "category": 4, + "is_locked": false, + "created_at": "2023-10-24T05:19:11.129-04:00", + "updated_at": "2023-10-24T05:19:24.330-04:00" + }, + { + "id": 1251258, + "name": "nicole_wolfgang", + "post_count": 4, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T05:19:10.341-04:00", + "category": 4, + "is_locked": false, + "created_at": "2023-10-24T05:19:11.109-04:00", + "updated_at": "2023-10-24T05:19:50.968-04:00" + }, + { + "id": 1251255, + "name": "nim_(razim)", + "post_count": 1, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T04:49:30.099-04:00", + "category": 4, + "is_locked": false, + "created_at": "2023-10-24T04:49:30.120-04:00", + "updated_at": "2023-10-24T04:50:10.978-04:00" + }, + { + "id": 1251254, + "name": "freddie_(freddie_as_f.r.o.7)", + "post_count": 1, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T04:42:53.579-04:00", + "category": 0, + "is_locked": false, + "created_at": "2023-10-24T04:42:53.791-04:00", + "updated_at": "2023-10-24T04:42:53.791-04:00" + }, + { + "id": 1251253, + "name": "low_pawpads", + "post_count": 1, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T04:42:22.631-04:00", + "category": 0, + "is_locked": false, + "created_at": "2023-10-24T04:42:22.658-04:00", + "updated_at": "2023-10-24T04:42:22.658-04:00" + }, + { + "id": 1251252, + "name": "white_multicolored_fur", + "post_count": 1, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T04:41:38.624-04:00", + "category": 0, + "is_locked": false, + "created_at": "2023-10-24T04:41:38.636-04:00", + "updated_at": "2023-10-24T04:41:38.636-04:00" + }, + { + "id": 1251251, + "name": "warrior_cats:_ultimate_edition", + "post_count": 1, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T04:26:51.145-04:00", + "category": 3, + "is_locked": false, + "created_at": "2023-10-24T04:26:52.665-04:00", + "updated_at": "2023-10-24T04:26:52.665-04:00" + }, + { + "id": 1251250, + "name": "little_snow_bee", + "post_count": 1, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T04:20:32.428-04:00", + "category": 0, + "is_locked": false, + "created_at": "2023-10-24T04:20:34.328-04:00", + "updated_at": "2023-10-24T04:20:34.328-04:00" + }, + { + "id": 1251249, + "name": "tacticoolmofo", + "post_count": 4, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T04:14:00.773-04:00", + "category": 1, + "is_locked": false, + "created_at": "2023-10-24T04:14:02.427-04:00", + "updated_at": "2023-10-24T04:14:02.427-04:00" + }, + { + "id": 1251248, + "name": "zotz_(artist)", + "post_count": 77, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T03:59:43.172-04:00", + "category": 1, + "is_locked": false, + "created_at": "2023-10-24T03:59:43.192-04:00", + "updated_at": "2023-10-24T03:59:52.829-04:00" + }, + { + "id": 1251246, + "name": "cum_in_arthropod_abdomen_cloaca", + "post_count": 1, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T03:35:58.957-04:00", + "category": 0, + "is_locked": false, + "created_at": "2023-10-24T03:35:58.965-04:00", + "updated_at": "2023-10-24T03:35:58.965-04:00" + }, + { + "id": 1251245, + "name": "dabubby", + "post_count": 1, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T03:28:51.607-04:00", + "category": 1, + "is_locked": false, + "created_at": "2023-10-24T03:28:53.065-04:00", + "updated_at": "2023-10-24T03:28:59.897-04:00" + }, + { + "id": 1251244, + "name": "big_thick_horse_bug_(reptilligator)", + "post_count": 6, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T03:19:04.737-04:00", + "category": 4, + "is_locked": false, + "created_at": "2023-10-24T03:19:04.762-04:00", + "updated_at": "2023-10-24T03:19:37.148-04:00" + }, + { + "id": 1251239, + "name": "sentient_ovum", + "post_count": 1, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T02:24:58.635-04:00", + "category": 0, + "is_locked": false, + "created_at": "2023-10-24T02:24:58.697-04:00", + "updated_at": "2023-10-24T02:24:58.697-04:00" + }, + { + "id": 1251237, + "name": "burningpalace", + "post_count": 1, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T02:10:48.280-04:00", + "category": 1, + "is_locked": false, + "created_at": "2023-10-24T02:10:49.544-04:00", + "updated_at": "2023-10-24T04:11:38.363-04:00" + }, + { + "id": 1251236, + "name": "gixer", + "post_count": 2, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T02:10:00.860-04:00", + "category": 4, + "is_locked": false, + "created_at": "2023-10-24T02:10:02.583-04:00", + "updated_at": "2023-10-24T02:10:22.835-04:00" + }, + { + "id": 1251235, + "name": "skirt_in_mouth", + "post_count": 2, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T02:07:20.977-04:00", + "category": 0, + "is_locked": false, + "created_at": "2023-10-24T02:07:21.041-04:00", + "updated_at": "2023-10-24T02:07:21.041-04:00" + }, + { + "id": 1251234, + "name": "lara_(fightinlove)", + "post_count": 1, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T02:05:15.564-04:00", + "category": 4, + "is_locked": false, + "created_at": "2023-10-24T02:05:20.184-04:00", + "updated_at": "2023-10-24T05:39:23.811-04:00" + }, + { + "id": 1251233, + "name": "jane_(wonder_b-cruise)", + "post_count": 1, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T02:03:30.108-04:00", + "category": 0, + "is_locked": false, + "created_at": "2023-10-24T02:03:30.144-04:00", + "updated_at": "2023-10-24T02:03:30.144-04:00" + }, + { + "id": 1251232, + "name": "tracy_(wonder_b-cruise)", + "post_count": 1, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T02:03:30.108-04:00", + "category": 0, + "is_locked": false, + "created_at": "2023-10-24T02:03:30.140-04:00", + "updated_at": "2023-10-24T02:03:30.140-04:00" + }, + { + "id": 1251231, + "name": "garland_(wonder_b-cruise)", + "post_count": 1, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T02:03:30.108-04:00", + "category": 0, + "is_locked": false, + "created_at": "2023-10-24T02:03:30.133-04:00", + "updated_at": "2023-10-24T02:03:30.133-04:00" + }, + { + "id": 1251230, + "name": "wonder_b-cruise", + "post_count": 1, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T01:59:16.044-04:00", + "category": 0, + "is_locked": false, + "created_at": "2023-10-24T01:59:16.066-04:00", + "updated_at": "2023-10-24T01:59:16.066-04:00" + }, + { + "id": 1251227, + "name": "sam_(shai_dreamcast)", + "post_count": 1, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T01:50:05.159-04:00", + "category": 4, + "is_locked": false, + "created_at": "2023-10-24T01:50:10.224-04:00", + "updated_at": "2023-10-24T06:50:56.134-04:00" + }, + { + "id": 1251226, + "name": "flip_(greenstranger)", + "post_count": 2, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T01:43:51.267-04:00", + "category": 4, + "is_locked": false, + "created_at": "2023-10-24T01:43:53.314-04:00", + "updated_at": "2023-10-24T01:43:53.314-04:00" + }, + { + "id": 1251225, + "name": "jade_(shai_dreamcast)", + "post_count": 1, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T01:43:48.317-04:00", + "category": 4, + "is_locked": false, + "created_at": "2023-10-24T01:43:49.808-04:00", + "updated_at": "2023-10-24T01:43:57.309-04:00" + }, + { + "id": 1251224, + "name": "eris_morgan", + "post_count": 6, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T01:41:22.193-04:00", + "category": 1, + "is_locked": false, + "created_at": "2023-10-24T01:41:22.896-04:00", + "updated_at": "2023-10-24T06:31:17.336-04:00" + }, + { + "id": 1251223, + "name": "heaven's_door", + "post_count": 1, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T01:40:19.716-04:00", + "category": 4, + "is_locked": false, + "created_at": "2023-10-24T01:40:19.734-04:00", + "updated_at": "2023-10-24T01:40:31.461-04:00" + }, + { + "id": 1251222, + "name": "lenore_(peachbumz)", + "post_count": 2, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T01:39:42.014-04:00", + "category": 4, + "is_locked": false, + "created_at": "2023-10-24T01:39:42.941-04:00", + "updated_at": "2023-10-24T01:39:42.941-04:00" + }, + { + "id": 1251221, + "name": "dawn_(bratcatt)", + "post_count": 2, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T01:27:57.857-04:00", + "category": 4, + "is_locked": false, + "created_at": "2023-10-24T01:27:58.852-04:00", + "updated_at": "2023-10-24T01:27:58.852-04:00" + }, + { + "id": 1251220, + "name": "solaris26", + "post_count": 1, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T01:27:51.773-04:00", + "category": 1, + "is_locked": false, + "created_at": "2023-10-24T01:27:52.818-04:00", + "updated_at": "2023-10-24T01:28:06.079-04:00" + }, + { + "id": 1251219, + "name": "xcookieeex", + "post_count": 1, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T01:25:12.335-04:00", + "category": 1, + "is_locked": false, + "created_at": "2023-10-24T01:25:16.580-04:00", + "updated_at": "2023-10-24T06:53:02.328-04:00" + }, + { + "id": 1251218, + "name": "cookie_(xcookieeex)", + "post_count": 1, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T01:25:12.335-04:00", + "category": 4, + "is_locked": false, + "created_at": "2023-10-24T01:25:16.561-04:00", + "updated_at": "2023-10-24T06:53:15.800-04:00" + }, + { + "id": 1251217, + "name": "tundra_(cosmicinteleon)", + "post_count": 1, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T01:25:03.856-04:00", + "category": 4, + "is_locked": false, + "created_at": "2023-10-24T01:25:07.436-04:00", + "updated_at": "2023-10-24T01:25:25.101-04:00" + }, + { + "id": 1251215, + "name": "stuck_in_box", + "post_count": 1, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T00:54:45.209-04:00", + "category": 0, + "is_locked": false, + "created_at": "2023-10-24T00:54:45.240-04:00", + "updated_at": "2023-10-24T00:54:45.240-04:00" + }, + { + "id": 1251214, + "name": "posquora1", + "post_count": 2, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T00:52:53.091-04:00", + "category": 1, + "is_locked": false, + "created_at": "2023-10-24T00:52:53.115-04:00", + "updated_at": "2023-10-24T00:52:53.115-04:00" + }, + { + "id": 1251212, + "name": "minty_mintleaf_(fishpaste2100)", + "post_count": 4, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T00:46:31.384-04:00", + "category": 0, + "is_locked": false, + "created_at": "2023-10-24T00:46:31.406-04:00", + "updated_at": "2023-10-24T00:46:31.406-04:00" + }, + { + "id": 1251211, + "name": "parlormaid", + "post_count": 1, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T00:45:30.371-04:00", + "category": 0, + "is_locked": false, + "created_at": "2023-10-24T00:45:31.026-04:00", + "updated_at": "2023-10-24T00:45:31.026-04:00" + }, + { + "id": 1251210, + "name": "solodimeleo", + "post_count": 2, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T00:36:02.136-04:00", + "category": 1, + "is_locked": false, + "created_at": "2023-10-24T00:36:05.350-04:00", + "updated_at": "2023-10-24T00:37:45.770-04:00" + }, + { + "id": 1251208, + "name": "klustr_jr", + "post_count": 1, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T00:33:51.994-04:00", + "category": 4, + "is_locked": false, + "created_at": "2023-10-24T00:33:52.014-04:00", + "updated_at": "2023-10-24T00:34:56.371-04:00" + }, + { + "id": 1251207, + "name": "bigal_(1-upclock)", + "post_count": 3, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T00:25:55.576-04:00", + "category": 0, + "is_locked": false, + "created_at": "2023-10-24T00:25:57.052-04:00", + "updated_at": "2023-10-24T00:25:57.052-04:00" + }, + { + "id": 1251204, + "name": "lulu_(1-upclock)", + "post_count": 3, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T00:20:12.918-04:00", + "category": 0, + "is_locked": false, + "created_at": "2023-10-24T00:20:14.797-04:00", + "updated_at": "2023-10-24T00:20:14.797-04:00" + }, + { + "id": 1251201, + "name": "fake_metal_big", + "post_count": 1, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T00:11:41.639-04:00", + "category": 4, + "is_locked": false, + "created_at": "2023-10-24T00:11:41.649-04:00", + "updated_at": "2023-10-24T00:11:41.649-04:00" + }, + { + "id": 1251200, + "name": "irene_(thixxen)", + "post_count": 2, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-24T00:06:10.688-04:00", + "category": 4, + "is_locked": false, + "created_at": "2023-10-24T00:06:15.685-04:00", + "updated_at": "2023-10-24T00:10:59.339-04:00" + }, + { + "id": 1251197, + "name": "pollianna_(monodreams)", + "post_count": 2, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-23T23:58:51.324-04:00", + "category": 4, + "is_locked": false, + "created_at": "2023-10-23T23:58:51.363-04:00", + "updated_at": "2023-10-23T23:59:16.944-04:00" + }, + { + "id": 1251196, + "name": "suspended_by_torso", + "post_count": 1, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-23T23:47:57.838-04:00", + "category": 0, + "is_locked": false, + "created_at": "2023-10-23T23:47:57.868-04:00", + "updated_at": "2023-10-23T23:47:57.868-04:00" + }, + { + "id": 1251195, + "name": "suspended_by_flipper", + "post_count": 1, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-23T23:47:57.838-04:00", + "category": 0, + "is_locked": false, + "created_at": "2023-10-23T23:47:57.858-04:00", + "updated_at": "2023-10-23T23:47:57.858-04:00" + }, + { + "id": 1251192, + "name": "pseudo_scat", + "post_count": 8, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-23T23:33:24.475-04:00", + "category": 0, + "is_locked": false, + "created_at": "2023-10-23T23:33:24.505-04:00", + "updated_at": "2023-10-23T23:33:24.505-04:00" + }, + { + "id": 1251191, + "name": "smooth_pelvis", + "post_count": 1, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-23T23:33:07.539-04:00", + "category": 0, + "is_locked": false, + "created_at": "2023-10-23T23:33:08.336-04:00", + "updated_at": "2023-10-23T23:33:08.336-04:00" + }, + { + "id": 1251190, + "name": "auzzy", + "post_count": 3, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-23T23:28:48.545-04:00", + "category": 4, + "is_locked": false, + "created_at": "2023-10-23T23:28:50.531-04:00", + "updated_at": "2023-10-23T23:29:00.248-04:00" + }, + { + "id": 1251189, + "name": "datowda", + "post_count": 1, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-23T23:18:49.452-04:00", + "category": 1, + "is_locked": false, + "created_at": "2023-10-23T23:18:50.368-04:00", + "updated_at": "2023-10-23T23:18:50.368-04:00" + }, + { + "id": 1251188, + "name": "linia_dedoldia", + "post_count": 2, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-23T23:00:31.757-04:00", + "category": 4, + "is_locked": false, + "created_at": "2023-10-23T23:00:37.697-04:00", + "updated_at": "2023-10-23T23:01:00.303-04:00" + }, + { + "id": 1251187, + "name": "pursena_adoldia", + "post_count": 2, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-23T23:00:31.757-04:00", + "category": 4, + "is_locked": false, + "created_at": "2023-10-23T23:00:37.690-04:00", + "updated_at": "2023-10-23T23:00:53.457-04:00" + }, + { + "id": 1251186, + "name": "galacteasers", + "post_count": 1, + "related_tags": "[]", + "related_tags_updated_at": "2023-10-23T22:57:13.026-04:00", + "category": 1, + "is_locked": false, + "created_at": "2023-10-23T22:57:15.489-04:00", + "updated_at": "2023-10-23T22:57:32.732-04:00" + } +] \ No newline at end of file diff --git a/tests/user.json b/tests/user.json new file mode 100644 index 0000000..8ed06dc --- /dev/null +++ b/tests/user.json @@ -0,0 +1,26 @@ +{ + "wiki_page_version_count": 0, + "artist_version_count": 0, + "pool_version_count": 0, + "forum_post_count": 0, + "comment_count": 1, + "flag_count": 0, + "favorite_count": 1454, + "positive_feedback_count": 0, + "neutral_feedback_count": 0, + "negative_feedback_count": 0, + "upload_limit": 10, + "id": 136501, + "created_at": "2014-04-23T07:08:04.080-04:00", + "name": "Selloo", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 5, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": 922595 +} \ No newline at end of file diff --git a/tests/users.json b/tests/users.json new file mode 100644 index 0000000..2a8133f --- /dev/null +++ b/tests/users.json @@ -0,0 +1,1127 @@ +[ + { + "id": 1689428, + "created_at": "2023-10-24T09:43:44.355-04:00", + "name": "frenzy_90", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689427, + "created_at": "2023-10-24T09:42:39.000-04:00", + "name": "furryboi7669", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689426, + "created_at": "2023-10-24T09:40:37.574-04:00", + "name": "orgen", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689425, + "created_at": "2023-10-24T09:38:43.338-04:00", + "name": "UnknownToBliss", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689424, + "created_at": "2023-10-24T09:38:40.659-04:00", + "name": "BTCB", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689423, + "created_at": "2023-10-24T09:36:07.868-04:00", + "name": "BlaiddBestBoy", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689422, + "created_at": "2023-10-24T09:35:59.596-04:00", + "name": "ggttfydyf", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689421, + "created_at": "2023-10-24T09:32:40.047-04:00", + "name": "DickHarrison", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689420, + "created_at": "2023-10-24T09:31:34.693-04:00", + "name": "geeza", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689419, + "created_at": "2023-10-24T09:31:32.454-04:00", + "name": "Mrchheaf", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689418, + "created_at": "2023-10-24T09:28:57.710-04:00", + "name": "TheRealFardus", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689417, + "created_at": "2023-10-24T09:26:44.820-04:00", + "name": "thewatcherr", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689416, + "created_at": "2023-10-24T09:25:23.822-04:00", + "name": "Bybuggine", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689415, + "created_at": "2023-10-24T09:23:10.402-04:00", + "name": "Bojonegoro", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689414, + "created_at": "2023-10-24T09:22:54.336-04:00", + "name": "Sjbekbs", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689413, + "created_at": "2023-10-24T09:21:53.118-04:00", + "name": "gewkersquad", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689412, + "created_at": "2023-10-24T09:21:26.277-04:00", + "name": "adkaj", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689411, + "created_at": "2023-10-24T09:19:23.007-04:00", + "name": "SSGTFluffy", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689410, + "created_at": "2023-10-24T09:18:07.578-04:00", + "name": "wkge", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689409, + "created_at": "2023-10-24T09:17:17.640-04:00", + "name": "TheBlueFop", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689408, + "created_at": "2023-10-24T09:15:34.877-04:00", + "name": "fatness", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689407, + "created_at": "2023-10-24T09:13:17.378-04:00", + "name": "muming7032", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689406, + "created_at": "2023-10-24T09:09:36.067-04:00", + "name": "WhoreEater", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689405, + "created_at": "2023-10-24T09:04:00.933-04:00", + "name": "Jejxndn", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689404, + "created_at": "2023-10-24T09:03:26.312-04:00", + "name": "stonehard", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689403, + "created_at": "2023-10-24T09:02:32.745-04:00", + "name": "SzymonTheProtogen", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689402, + "created_at": "2023-10-24T09:00:04.801-04:00", + "name": "fbdsewrevew", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689401, + "created_at": "2023-10-24T08:59:41.927-04:00", + "name": "FemboyFeeler123", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689400, + "created_at": "2023-10-24T08:59:31.085-04:00", + "name": "Tinnie_33", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689399, + "created_at": "2023-10-24T08:58:08.077-04:00", + "name": "noname58", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689398, + "created_at": "2023-10-24T08:56:56.108-04:00", + "name": "D-Tritus", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689397, + "created_at": "2023-10-24T08:56:32.615-04:00", + "name": "Evee123", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689396, + "created_at": "2023-10-24T08:55:53.942-04:00", + "name": "ITISIT_song", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689395, + "created_at": "2023-10-24T08:54:36.651-04:00", + "name": "Genevapension", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689394, + "created_at": "2023-10-24T08:54:10.346-04:00", + "name": "bubba145", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689393, + "created_at": "2023-10-24T08:53:51.009-04:00", + "name": "Sneefee", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689392, + "created_at": "2023-10-24T08:50:09.804-04:00", + "name": "qwerwonner852", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689391, + "created_at": "2023-10-24T08:47:47.549-04:00", + "name": "pet_of_awex", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689390, + "created_at": "2023-10-24T08:46:35.300-04:00", + "name": "kkiioo", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689389, + "created_at": "2023-10-24T08:44:24.692-04:00", + "name": "uip93979", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689388, + "created_at": "2023-10-24T08:44:14.435-04:00", + "name": "afreaking_weeb", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689387, + "created_at": "2023-10-24T08:43:08.501-04:00", + "name": "GalacticTransport55", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689386, + "created_at": "2023-10-24T08:37:06.246-04:00", + "name": "Testing123123", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689385, + "created_at": "2023-10-24T08:35:10.276-04:00", + "name": "Skelly6969", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689384, + "created_at": "2023-10-24T08:27:49.662-04:00", + "name": "Wickerbeans", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689383, + "created_at": "2023-10-24T08:23:54.611-04:00", + "name": "Peponi", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689382, + "created_at": "2023-10-24T08:22:33.687-04:00", + "name": "Sansa39", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689381, + "created_at": "2023-10-24T08:17:20.862-04:00", + "name": "zoraskigalaxy", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689380, + "created_at": "2023-10-24T08:17:11.786-04:00", + "name": "himy", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689379, + "created_at": "2023-10-24T08:09:09.343-04:00", + "name": "Pokeb0yyy", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689378, + "created_at": "2023-10-24T08:08:14.141-04:00", + "name": "hsgshsgs", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689377, + "created_at": "2023-10-24T08:05:14.793-04:00", + "name": "LumineTheBean767", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689376, + "created_at": "2023-10-24T08:03:31.991-04:00", + "name": "LibroProhibito", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689375, + "created_at": "2023-10-24T08:02:52.851-04:00", + "name": "Xero_Grade", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689374, + "created_at": "2023-10-24T08:00:32.398-04:00", + "name": "UnusualDerg", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689373, + "created_at": "2023-10-24T07:59:39.940-04:00", + "name": "Moana_Liza", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689372, + "created_at": "2023-10-24T07:54:25.394-04:00", + "name": "Biggs777", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689371, + "created_at": "2023-10-24T07:47:30.373-04:00", + "name": "Hhghf234", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689370, + "created_at": "2023-10-24T07:45:08.376-04:00", + "name": "ldiot_Lizard", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689369, + "created_at": "2023-10-24T07:44:57.586-04:00", + "name": "Fausto17", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689368, + "created_at": "2023-10-24T07:42:29.120-04:00", + "name": "sinnerknight", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689367, + "created_at": "2023-10-24T07:33:29.024-04:00", + "name": "tam_baker23", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689366, + "created_at": "2023-10-24T07:32:56.185-04:00", + "name": "thinhlun2013", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689365, + "created_at": "2023-10-24T07:30:48.676-04:00", + "name": "Jfjfjgjn", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689364, + "created_at": "2023-10-24T07:29:06.268-04:00", + "name": "doggything", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689363, + "created_at": "2023-10-24T07:27:28.681-04:00", + "name": "Circumstances", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689362, + "created_at": "2023-10-24T07:25:13.712-04:00", + "name": "chenyufeng", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689361, + "created_at": "2023-10-24T07:24:12.915-04:00", + "name": "Comblue", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689360, + "created_at": "2023-10-24T07:14:41.771-04:00", + "name": "KomputerB155", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689359, + "created_at": "2023-10-24T07:11:02.866-04:00", + "name": "Kumanomi", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689358, + "created_at": "2023-10-24T07:09:27.056-04:00", + "name": "piz21", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689357, + "created_at": "2023-10-24T07:05:31.435-04:00", + "name": "ahmedmohsen", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689356, + "created_at": "2023-10-24T07:04:08.237-04:00", + "name": "Redphionix", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689355, + "created_at": "2023-10-24T07:02:04.583-04:00", + "name": "Shsvsv", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + }, + { + "id": 1689354, + "created_at": "2023-10-24T07:01:28.798-04:00", + "name": "mavge", + "level": 20, + "base_upload_limit": 10, + "post_upload_count": 0, + "post_update_count": 0, + "note_update_count": 0, + "is_banned": false, + "can_approve_posts": false, + "can_upload_free": false, + "level_string": "Member", + "avatar_id": null + } +] \ No newline at end of file