Compare commits

...

No commits in common. "master" and "main" have entirely different histories.
master ... main

105 changed files with 20740 additions and 1394 deletions

16
.editorconfig Normal file
View File

@ -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

View File

@ -1,29 +0,0 @@
name: Gitea Build Check
run-name: ${{ gitea.actor }} is testing the build
on:
pull_request:
branches: [ "master" ]
jobs:
Build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Go environment
uses: https://github.com/actions/setup-go@v3
with:
# The Go version to download (if necessary) and use. Supports semver spec and ranges.
go-version: 1.21.3 # optional
# Path to the go.mod file.
go-version-file: ./go.mod # optional
# Set this option to true if you want the action to always check for the latest available version that satisfies the version spec
check-latest: true # optional
# Used to specify whether caching is needed. Set to true, if you'd like to enable caching.
cache: true # optional
- name: Build
run: go build -v ./...
- name: Test
run: go test -v ./... -json -coverprofile=coverage.out > test-report.out

106
.gitignore vendored
View File

@ -1,6 +1,7 @@
# File created using '.gitignore Generator' for Visual Studio Code: https://bit.ly/vscode-gig
# Created by https://www.toptal.com/developers/gitignore/api/windows,visualstudiocode,goland,go
# Edit at https://www.toptal.com/developers/gitignore?templates=windows,visualstudiocode,goland,go
# 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:
@ -25,7 +26,7 @@
# Go workspace file
go.work
### GoLand ###
### 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
@ -104,58 +105,62 @@ fabric.properties
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### GoLand Patch ###
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
### GoLand+all Patch ###
# Ignore everything but code style settings and run configurations
# that are supposed to be shared within teams.
# *.iml
# modules.xml
# .idea/misc.xml
# *.ipr
.idea/*
# Sonarlint plugin
# https://plugins.jetbrains.com/plugin/7973-sonarlint
.idea/**/sonarlint/
!.idea/codeStyles
!.idea/runConfigurations
# SonarQube Plugin
# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
.idea/**/sonarIssues.xml
### Linux ###
*~
# Markdown Navigator plugin
# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
.idea/**/markdown-navigator.xml
.idea/**/markdown-navigator-enh.xml
.idea/**/markdown-navigator/
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# Cache file creation bug
# See https://youtrack.jetbrains.com/issue/JBR-2257
.idea/$CACHE_FILE$
# KDE directory preferences
.directory
# CodeStream plugin
# https://plugins.jetbrains.com/plugin/12206-codestream
.idea/codestream.xml
# Linux trash folder which might appear on any partition or disk
.Trash-*
# Azure Toolkit for IntelliJ plugin
# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij
.idea/**/azureSettings.xml
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride
# Local History for Visual Studio Code
.history/
# Icon must end with two \r
Icon
# Built Visual Studio Code Extensions
*.vsix
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
.ionide
# 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
@ -183,12 +188,7 @@ $RECYCLE.BIN/
# Windows shortcuts
*.lnk
# End of https://www.toptal.com/developers/gitignore/api/windows,visualstudiocode,goland,go
# End of https://www.toptal.com/developers/gitignore/api/windows,linux,goland+all,macos,go
/.scannerwork/
# Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option)
.idea/*
/.run/*
.env
*.env

View File

@ -0,0 +1,12 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="go test e621-sdk-go (endpoints)" type="GoTestRunConfiguration" factoryName="Go Test">
<module name="e621-sdk-go" />
<working_directory value="$PROJECT_DIR$" />
<kind value="DIRECTORY" />
<package value="git.dragse.it/anthrove/e621-sdk-go" />
<directory value="$PROJECT_DIR$/pkg/e621/endpoints" />
<filePath value="$PROJECT_DIR$" />
<framework value="gotest" />
<method v="2" />
</configuration>
</component>

131
README.MD
View File

@ -1,131 +0,0 @@
# e621 to Graph
Scrapes users Favorite Posts and Upload them to a graph based database.
## Prerequisites
Before running the program, ensure that you have the following installed on your system:
- Go (version 1.20.2 or higher)
- Docker (optional, if you plan to use Docker)
## Installation
To install the project, follow these steps:
1. Clone the repository:
```shell
git clone https://git.dragse.it/fenpaws/e621-to-graph
```
2. Change into the project's directory:
```shell
cd e621-to-graph
```
3. Install the required dependencies:
```shell
go mod download
go mod verify
```
## Setting Up Environment Variables
The program requires certain environment variables to be set.
```plaintext
E621_API_KEY=
E621_USERNAME=
DB_TYPE=neo4j
DB_URL=
DB_PASSWORD=
DB_USERNAME=
# Allowed values are FATAL, ERROR, WARN, INFO, DEBUG, TRACE (default: INFO)
LOG_LEVEL=
# Allowed values are PLAIN, JSON (default: PLAIN)
LOG_FORMAT=
# Allowed values are TRUE, FALSE (default: FALSE)
NEO4J_DEBUG=
```
## Running the Program
To run the program, execute the following command:
```shell
go run main.go
```
## Building the Program
If you want to build the program into an executable binary, use the following command:
```shell
go build
```
## Docker Support
The program can also be run using Docker. To use Docker, follow these steps:
1. Create a .env file in the project's root directory with the following content:
```plaintext
E621_API_KEY=
E621_USERNAME=
DB_TYPE=neo4j
DB_URL=
DB_PASSWORD=
DB_USERNAME=
# Allowed values are FATAL, ERROR, WARN, INFO, DEBUG, TRACE (default: INFO)
LOG_LEVEL=
# Allowed values are PLAIN, JSON (default: PLAIN)
LOG_FORMAT=
# Allowed values are TRUE, FALSE (default: FALSE)
NEO4J_DEBUG=
```
2. Build the Docker image:
```shell
docker compose build
```
3. Run the Docker container:
```shell
docker compose up -d
```
## Database Support
The program supports the following databases.
### Neo4j
To improve performance, it is recommended to create indices on different nodes in Neo4j.
Run the following commands to create the required indices:
```shell
neo4j$ CREATE INDEX tagIndex FOR (t:e621Tag) ON (t.e621Tag);
neo4j$ CREATE INDEX postIndex FOR (p:e621Post) ON (p.e621PostID);
neo4j$ CREATE INDEX sourceUrlIndex FOR (s:Source) ON (s.URL);
neo4j$ CREATE INDEX userIdIndex FOR (u:e621User) ON (u.e621ID);
```
### Memgraph:
To improve performance, it is recommended to create indices on different nodes in Memgraph.
Run the following commands to create the required indices:
```shell
memgraph> CREATE INDEX ON :e621Tag(e621Tag);
memgraph> CREATE INDEX ON :e621Post(e621PostID);
memgraph> CREATE INDEX ON :Source(URL);
memgraph> CREATE INDEX ON :e621User(e621ID);
```

67
README.md Normal file
View File

@ -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: | |

View File

@ -1,22 +0,0 @@
FROM golang:alpine as builder
WORKDIR /go/src/git.dragse.it/fenpaws/e621-to-graph
RUN apk add -U --no-cache ca-certificates && update-ca-certificates
COPY go.mod go.sum ./
RUN go mod download
COPY . ./
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -installsuffix cgo -ldflags "-w -s" -o /app ./cmd/scraper/
FROM scratch
WORKDIR /
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /app ./
EXPOSE 8080
CMD ["/app"]

View File

@ -1,3 +0,0 @@
set GOARCH=amd64
set GOOS=linux
go build

View File

@ -1,97 +0,0 @@
package main
import (
"context"
"git.dragse.it/anthrove/e621-sdk-go/pkg/e621"
"git.dragse.it/anthrove/e621-to-graph/internal/api"
"git.dragse.it/anthrove/e621-to-graph/internal/config"
"git.dragse.it/anthrove/e621-to-graph/internal/database/neo4j"
"git.dragse.it/anthrove/e621-to-graph/pkg/logic"
log "github.com/sirupsen/logrus"
"net/http"
"strings"
"time"
)
func main() {
// Loads Config
appConfig, err := config.LoadConfig()
if err != nil {
log.Fatal(err)
}
log.Debug("main: config loaded")
loggingSetup(appConfig)
var graphConnection logic.GraphConnection
ctx := context.Background()
switch strings.ToLower(appConfig.DBType) {
case "neo4j":
graphConnection = neo4j.NewNeo4JConnection(appConfig.Neo4jDebug)
err = graphConnection.Connect(ctx, appConfig.DBEndpoint, appConfig.DBUsername, appConfig.DBPassword)
if err != nil {
log.Panicf("main: %s", err)
}
default:
log.Panic("main: no database was selected!")
}
log.WithFields(log.Fields{
"database_type": strings.ToLower(appConfig.DBType),
}).Info("main: database connection successful")
// Initialize the e621API
client := e621.NewClient(appConfig.E621Username, appConfig.E621APIKey)
// Register the ScapeUserFavourites with the "/user" route
http.HandleFunc("/user", api.ScapeUserFavourites(ctx, graphConnection, &client))
// Start the HTTP server
err = http.ListenAndServe(":8080", nil)
if err != nil {
log.Panicf("main: %s", err)
}
}
func loggingSetup(appConfig *config.Config) {
switch strings.ToUpper(appConfig.LogLevel) {
case "FATAL":
log.SetLevel(log.FatalLevel)
case "ERROR":
log.SetLevel(log.ErrorLevel)
case "WARN":
log.SetLevel(log.WarnLevel)
case "INFO":
log.SetLevel(log.InfoLevel)
case "DEBUG":
log.SetLevel(log.DebugLevel)
case "TRACE":
log.SetLevel(log.TraceLevel)
default:
log.Panic("main: no log level was set")
}
switch strings.ToUpper(appConfig.LogFormat) {
case "PLAIN":
log.SetFormatter(&log.TextFormatter{
ForceColors: true,
ForceQuote: true,
DisableLevelTruncation: true,
PadLevelText: true,
FullTimestamp: true,
TimestampFormat: time.DateTime, // 2006-01-02 15:04:05
})
case "JSON":
log.SetFormatter(&log.JSONFormatter{
TimestampFormat: time.DateTime, // 2006-01-02 15:04:05,
})
default:
log.Panic("main: no formatter was set")
}
log.WithFields(log.Fields{
"log_level": log.GetLevel(),
"formatter": appConfig.LogFormat,
}).Info("main: setting logging info")
}

View File

@ -1,23 +0,0 @@
version: '2'
services:
neo4j:
image: bitnami/neo4j:latest
ports:
- '7474:7474'
- '7473:7473'
- '7687:7687'
volumes:
- 'neo4j_data:/bitnami'
redis:
image: 'bitnami/redis:latest'
environment:
- REDIS_PASSWORD=password123
ports:
- '6379:6379'
volumes:
- 'redis_data:/bitnami'
volumes:
neo4j_data:
driver: local
redis_data:
driver: local

View File

@ -1,11 +0,0 @@
services:
app:
restart: unless-stopped
image: anthrove/e621-to-graph:latest
build:
context: ../.
dockerfile: build/package/Dockerfile
ports:
- 8080:8080
env_file: .env

View File

@ -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)
}
}

45
example/highlevel/post.go Normal file
View File

@ -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))
}

23
example/highlevel/user.go Normal file
View File

@ -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)
}

65
example/lowlevel/dmail.go Normal file
View File

@ -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("----------")
}

View File

@ -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("----------")
}

57
example/lowlevel/note.go Normal file
View File

@ -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("----------")
}

57
example/lowlevel/pool.go Normal file
View File

@ -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("----------")
}

57
example/lowlevel/post.go Normal file
View File

@ -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("----------")
}

97
example/lowlevel/tag.go Normal file
View File

@ -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("----------")
}

57
example/lowlevel/user.go Normal file
View File

@ -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("----------")
}

43
example/midlevel/dmail.go Normal file
View File

@ -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("----------")
}

View File

@ -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("----------")
}

58
example/midlevel/note.go Normal file
View File

@ -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("----------")
}

57
example/midlevel/pool.go Normal file
View File

@ -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("----------")
}

68
example/midlevel/post.go Normal file
View File

@ -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("----------")
}

81
example/midlevel/tag.go Normal file
View File

@ -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("----------")
}

54
example/midlevel/user.go Normal file
View File

@ -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("----------")
}

16
go.mod
View File

@ -1,17 +1,11 @@
module git.dragse.it/anthrove/e621-to-graph
module git.dragse.it/anthrove/e621-sdk-go
go 1.21.3
require (
git.dragse.it/anthrove/e621-sdk-go v1.3.0
github.com/caarlos0/env v3.5.0+incompatible
github.com/neo4j/neo4j-go-driver/v5 v5.8.1
github.com/sirupsen/logrus v1.9.3
github.com/jarcoal/httpmock v1.3.1
github.com/joho/godotenv v1.5.1
golang.org/x/time v0.3.0
)
require (
github.com/joho/godotenv v1.5.1 // indirect
golang.org/x/sys v0.9.0 // indirect
golang.org/x/time v0.3.0 // indirect
)
require golang.org/x/net v0.18.0 // indirect

24
go.sum
View File

@ -1,28 +1,12 @@
git.dragse.it/anthrove/e621-sdk-go v1.3.0 h1:sbjrOps4WxXf42kyNb8ZVxC6dCiFrCkqv4C8BgKkIGA=
git.dragse.it/anthrove/e621-sdk-go v1.3.0/go.mod h1:urFl0jjx2UK8Vz1tMI253CvWK8fZqd/a9+xdE8ZBwq4=
github.com/caarlos0/env v3.5.0+incompatible h1:Yy0UN8o9Wtr/jGHZDpCBLpNrzcFLLM2yixi/rBrKyJs=
github.com/caarlos0/env v3.5.0+incompatible/go.mod h1:tdCsowwCzMLdkqRYDlHpZCp2UooDD3MspDBjZ2AD02Y=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/neo4j/neo4j-go-driver/v5 v5.8.1 h1:IysKg6KJIUgyItmnHRRrt2N8srbd6znMslRW3qQErTQ=
github.com/neo4j/neo4j-go-driver/v5 v5.8.1/go.mod h1:Vff8OwT7QpLm7L2yYr85XNWe9Rbqlbeb9asNXJTHO4k=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -1,39 +0,0 @@
package api
import (
"context"
"fmt"
"git.dragse.it/anthrove/e621-sdk-go/pkg/e621"
"git.dragse.it/anthrove/e621-to-graph/internal/service"
"git.dragse.it/anthrove/e621-to-graph/pkg/logic"
log "github.com/sirupsen/logrus"
"net/http"
)
// ScapeUserFavourites is the handler for the user API
func ScapeUserFavourites(ctx context.Context, graphConnection logic.GraphConnection, client *e621.Client) func(response http.ResponseWriter, request *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
w.WriteHeader(http.StatusMethodNotAllowed)
fmt.Fprintf(w, "only POST requests are allowed")
return
}
username := r.FormValue("username")
if username == "" {
w.WriteHeader(http.StatusBadRequest)
fmt.Fprintf(w, "username is required")
return
}
// Perform further processing with the username
go service.ScrapeUser(ctx, graphConnection, client, username)
// Send a response
w.WriteHeader(http.StatusOK)
log.WithFields(log.Fields{
"requested_user": username,
}).Info("api: processing user")
}
}

View File

@ -1,40 +0,0 @@
package config
import (
"fmt"
"github.com/caarlos0/env"
"strings"
)
type Config struct {
E621APIKey string `env:"E621_API_KEY,required"`
E621Username string `env:"E621_USERNAME,required"`
DBType string `env:"DB_TYPE,required"`
DBEndpoint string `env:"DB_URL,required"`
DBUsername string `env:"DB_USERNAME,required"`
DBPassword string `env:"DB_PASSWORD,required"`
LogLevel string `env:"LOG_LEVEL" envDefault:"INFO"`
LogFormat string `env:"LOG_FORMAT" envDefault:"PLAIN"`
Neo4jDebug bool `env:"NEO4J_DEBUG" envDefault:"FALSE"`
}
// LoadConfig loads the configuration from environment variables
func LoadConfig() (*Config, error) {
config := &Config{}
if err := env.Parse(config); err != nil {
return nil, fmt.Errorf("config: error parsing configuration: %w", err)
}
logLevel := strings.ToUpper(config.LogLevel)
logFormat := strings.ToUpper(config.LogFormat)
if logLevel != "FATAL" && logLevel != "ERROR" && logLevel != "WARN" && logLevel != "INFO" && logLevel != "DEBUG" && logLevel != "TRACE" {
return nil, fmt.Errorf("config: valid levels for erros are: FATAL, ERROR, WARN, INFO, DEBUG, TRACE")
}
if logFormat != "PLAIN" && logFormat != "JSON" {
return nil, fmt.Errorf("config: valit formatters are: PLAIN, JSON")
}
return config, nil
}

View File

@ -1,78 +0,0 @@
package neo4j
import (
"context"
"git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model"
"git.dragse.it/anthrove/e621-to-graph/pkg/logic"
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
"github.com/neo4j/neo4j-go-driver/v5/neo4j/config"
)
type neo4jConnection struct {
driver neo4j.DriverWithContext
neo4jDebug bool
}
func NewNeo4JConnection(neo4jDebug bool) logic.GraphConnection {
return &neo4jConnection{
driver: nil,
neo4jDebug: neo4jDebug,
}
}
func (c *neo4jConnection) GetUserFavoriteCount(ctx context.Context, userID model.UserID) (int64, error) {
return GetUserFavoritesCount(ctx, c.driver, userID)
}
func (c *neo4jConnection) CheckUserToPostLink(ctx context.Context, e621PostID model.PostID, e621UserID model.UserID) (bool, error) {
return CheckUserToPostLink(ctx, c.driver, e621PostID, e621UserID)
}
func (c *neo4jConnection) EstablishPostToTagLink(ctx context.Context, e621PostID model.PostID, e621Tag string) error {
return EstablishPostTagLink(ctx, c.driver, e621PostID, e621Tag)
}
func (c *neo4jConnection) EstablishPostToSourceLink(ctx context.Context, e621PostID model.PostID, sourceURL string) error {
return EstablishPostToSourceLink(ctx, c.driver, e621PostID, sourceURL)
}
func (c *neo4jConnection) EstablishUserToPostLink(ctx context.Context, e621PostID model.PostID, e621UserID model.UserID) error {
return EstablishUserToPostLink(ctx, c.driver, e621PostID, e621UserID)
}
func (c *neo4jConnection) UploadTag(ctx context.Context, name string, tagType string) error {
return CreateTagNode(ctx, c.driver, name, tagType)
}
func (c *neo4jConnection) UploadPost(ctx context.Context, postID model.PostID) error {
return CreatePostNode(ctx, c.driver, postID)
}
func (c *neo4jConnection) UploadSource(ctx context.Context, SourceURL string) error {
return CreateSourceNode(ctx, c.driver, SourceURL)
}
func (c *neo4jConnection) UploadUser(ctx context.Context, user model.User) error {
return CreateUserNode(ctx, c.driver, user)
}
func (c *neo4jConnection) Connect(ctx context.Context, endpoint string, username string, password string) error {
driver, err := neo4j.NewDriverWithContext(endpoint, neo4j.BasicAuth(username, password, ""),
logger(c.neo4jDebug))
if err != nil {
return err
}
err = driver.VerifyAuthentication(ctx, nil)
if err != nil {
return err
}
c.driver = driver
return nil
}
func logger(neo4jDebug bool) func(config *config.Config) {
return func(config *config.Config) {
config.Log = NewNeo4jLogger(neo4jDebug)
}
}

View File

@ -1,45 +0,0 @@
package neo4j
import (
"fmt"
neo4jLog "github.com/neo4j/neo4j-go-driver/v5/neo4j/log"
log "github.com/sirupsen/logrus"
)
type neo4jLogger struct {
neo4jDebug bool
}
func NewNeo4jLogger(neo4jDebug bool) neo4jLog.Logger {
return &neo4jLogger{neo4jDebug: neo4jDebug}
}
func (n neo4jLogger) Error(name string, id string, err error) {
log.WithFields(log.Fields{
"name": name,
"id": id,
}).Errorf("neo4j: %s", err)
}
func (n neo4jLogger) Warnf(name string, id string, msg string, args ...any) {
log.WithFields(log.Fields{
"name": name,
"id": id,
}).Warnf("neo4j: %v", fmt.Sprintf(msg, args...))
}
func (n neo4jLogger) Infof(name string, id string, msg string, args ...any) {
log.WithFields(log.Fields{
"name": name,
"id": id,
}).Infof("neo4j: %v", fmt.Sprintf(msg, args...))
}
func (n neo4jLogger) Debugf(name string, id string, msg string, args ...any) {
if n.neo4jDebug {
log.WithFields(log.Fields{
"name": name,
"id": id,
}).Debugf("neo4j: %v", fmt.Sprintf(msg, args...))
}
}

View File

@ -1,6 +0,0 @@
package model
type DBTag struct {
Tag string
TagType string
}

View File

@ -1,23 +0,0 @@
package neo4j
import (
"context"
"git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model"
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
)
func CreatePostNode(ctx context.Context, driver neo4j.DriverWithContext, postID model.PostID) error {
query := `
MERGE (u:e621Post {e621PostID: $postID});
`
params := map[string]any{
"postID": postID,
}
_, err := neo4j.ExecuteQuery(ctx, driver, query, params, neo4j.EagerResultTransformer)
if err != nil {
return err
}
return nil
}

View File

@ -1,103 +0,0 @@
package neo4j
import (
"context"
"git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model"
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
log "github.com/sirupsen/logrus"
)
func EstablishPostTagLink(ctx context.Context, driver neo4j.DriverWithContext, e621PostID model.PostID, e621Tag string) error {
query := `
MATCH (p:e621Post {e621PostID: $e621PostID})
MATCH (t:e621Tag {e621Tag: $e621Tag})
MERGE (p)-[:HAS_TAG]->(t);
`
params := map[string]interface{}{
"e621PostID": e621PostID,
"e621Tag": e621Tag,
}
log.WithFields(log.Fields{
"e621_post_id": e621PostID,
"e621_tag": e621Tag,
}).Trace("neo4j: creating post to e621Tag link")
_, err := neo4j.ExecuteQuery(ctx, driver, query, params, neo4j.EagerResultTransformer)
if err != nil {
return err
}
return nil
}
func EstablishPostToSourceLink(ctx context.Context, driver neo4j.DriverWithContext, e621PostID model.PostID, sourceURL string) error {
query := `
MATCH (p:e621Post {e621PostID: $e621PostID})
MATCH (s:Source {URL: $sourceURL})
MERGE (p)-[:HAS_SOURCE]->(s)
`
params := map[string]interface{}{
"e621PostID": e621PostID,
"sourceURL": sourceURL,
}
log.WithFields(log.Fields{
"e621_post_id": e621PostID,
"source_url": sourceURL,
}).Trace("neo4j: creating post to source link")
_, err := neo4j.ExecuteQuery(ctx, driver, query, params, neo4j.EagerResultTransformer)
if err != nil {
return err
}
return nil
}
func EstablishUserToPostLink(ctx context.Context, driver neo4j.DriverWithContext, e621PostID model.PostID, e621UserID model.UserID) error {
query := `
MATCH (p:e621Post {e621PostID: $e621PostID})
MATCH (u:e621User {e621ID: $e621ID})
MERGE (u)-[:IS_FAVORITE]->(p)
`
params := map[string]interface{}{
"e621PostID": e621PostID,
"e621ID": e621UserID,
}
log.WithFields(log.Fields{
"e621_post_id": e621PostID,
"e621_user_id": e621UserID,
}).Trace("neo4j: creating user to post link")
_, err := neo4j.ExecuteQuery(ctx, driver, query, params, neo4j.EagerResultTransformer)
if err != nil {
return err
}
return nil
}
// CheckUserToPostLink gives back a bool if the connection between the post and the user exists
func CheckUserToPostLink(ctx context.Context, driver neo4j.DriverWithContext, e621PostID model.PostID, e621UserID model.UserID) (bool, error) {
query := `
MATCH (user:e621User {e621ID: $e621ID})-[favorite:IS_FAVORITE]->(post:e621Post {e621PostID: $e621PostID})
RETURN COUNT(favorite) > 0 AS isFavorite
`
params := map[string]interface{}{
"e621PostID": e621PostID,
"e621ID": e621UserID,
}
log.WithFields(log.Fields{
"e621_post_id": e621PostID,
"e621_user_id": e621UserID,
}).Trace("neo4j: check user post relationship")
result, err := neo4j.ExecuteQuery(ctx, driver, query, params, neo4j.EagerResultTransformer)
if err != nil {
return false, err
}
exists, _, err := neo4j.GetRecordValue[bool](result.Records[0], "isFavorite")
if err != nil {
return false, err
}
return exists, nil
}

View File

@ -1,22 +0,0 @@
package neo4j
import (
"context"
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
)
func CreateSourceNode(ctx context.Context, driver neo4j.DriverWithContext, URL string) error {
query := `
MERGE (u:Source {URL: $url});
`
params := map[string]any{
"url": URL,
}
_, err := neo4j.ExecuteQuery(ctx, driver, query, params, neo4j.EagerResultTransformer)
if err != nil {
return err
}
return nil
}

View File

@ -1,60 +0,0 @@
package neo4j
import (
"context"
"git.dragse.it/anthrove/e621-to-graph/internal/database/neo4j/model"
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
)
func CreateTagNode(ctx context.Context, driver neo4j.DriverWithContext, name string, tagType string) error {
query := `
MERGE (u:e621Tag {e621Tag: $name, e621TagType: $tagType});
`
params := map[string]interface{}{
"name": name,
"tagType": tagType,
}
_, err := neo4j.ExecuteQuery(ctx, driver, query, params, neo4j.EagerResultTransformer)
if err != nil {
return err
}
return nil
}
func GetTagNodeByName(ctx context.Context, driver neo4j.DriverWithContext, name string) (model.DBTag, bool, error) {
var tag model.DBTag
query := `
MATCH (u:e621Tag {e621Tag: $name})
RETURN u.e621Tag AS e621Tag, u.e621TagType AS e621TagType;
`
params := map[string]interface{}{
"name": name,
}
result, err := neo4j.ExecuteQuery(ctx, driver, query, params, neo4j.EagerResultTransformer)
if err != nil {
return tag, false, err
}
if len(result.Records) > 0 {
record := result.Records[0]
e621Tag, _, _ := neo4j.GetRecordValue[string](record, "e621Tag")
e621TagType, _, _ := neo4j.GetRecordValue[string](record, "e621TagType")
tag = model.DBTag{
Tag: e621Tag,
TagType: e621TagType,
}
if e621Tag != name {
return tag, false, nil
}
return tag, true, nil
}
return tag, false, err
}

View File

@ -1,54 +0,0 @@
package neo4j
import (
"context"
"git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model"
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
)
func CreateUserNode(ctx context.Context, driver neo4j.DriverWithContext, user model.User) error {
query := `
MERGE (u:e621User {e621ID: $id, e621Username: $name});
`
params := map[string]interface{}{
"id": user.ID,
"name": user.Name,
}
_, err := neo4j.ExecuteQuery(ctx, driver, query, params, neo4j.EagerResultTransformer)
if err != nil {
return err
}
return nil
}
func GetUserFavoritesCount(ctx context.Context, driver neo4j.DriverWithContext, userID model.UserID) (int64, error) {
var userFavoriteCount int64
query := `
MATCH (:e621User {e621ID: $userID})-[:IS_FAVORITE]->(:e621Post)
RETURN count(*) AS numberOfFavoritedPosts;
`
params := map[string]interface{}{
"userID": userID,
}
result, err := neo4j.ExecuteQuery(ctx, driver, query, params, neo4j.EagerResultTransformer)
if err != nil {
return 0, err
}
if len(result.Records) == 0 {
// no matches -> user does not exist, return count 0
return userFavoriteCount, err
}
record := result.Records[0]
userFavoriteCount, _, err = neo4j.GetRecordValue[int64](record, "numberOfFavoritedPosts")
if err != nil {
return userFavoriteCount, err
}
return userFavoriteCount, nil
}

View File

@ -1,299 +0,0 @@
package service
import (
"context"
"git.dragse.it/anthrove/e621-sdk-go/pkg/e621"
"git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model"
"git.dragse.it/anthrove/e621-to-graph/pkg/logic"
"git.dragse.it/anthrove/e621-to-graph/pkg/util"
log "github.com/sirupsen/logrus"
"time"
)
func ScrapeUser(ctx context.Context, graphConnection logic.GraphConnection, client *e621.Client, username string) error {
var err error
scrapeTime := time.Now()
e621User, err := client.GetUserByName(username).Execute()
if err != nil {
return err
}
if e621User.IsBanned {
log.WithFields(log.Fields{
"e621_username": e621User.Name,
"e621_user_id": e621User.ID,
"e621_user_bann": e621User.IsBanned,
}).Info("service: user is Banned")
return nil
}
err = graphConnection.UploadUser(ctx, e621User)
if err != nil {
return err
}
currentDBFavCount, err := graphConnection.GetUserFavoriteCount(ctx, e621User.ID)
if err != nil {
return err
}
favoriteBuilder, err := client.GetFavoritesForUser(e621User.Name)
if err != nil {
return err
}
if currentDBFavCount > e621User.FavoriteCount {
//TODO: IMPLEMENT USER MARKED FOR DELETED FAVS
log.WithFields(log.Fields{
"e621_username": e621User.Name,
"e621_user_id": e621User.ID,
"e621_current_db_favorite_count": currentDBFavCount,
"e621_user_favorite_count": e621User.FavoriteCount,
}).Debug("service: user has favorites deleted")
}
var pageIndex = 1
for currentDBFavCount < e621User.FavoriteCount {
favorites, err := favoriteBuilder.Page(pageIndex).Execute()
if err != nil {
return err
}
if len(favorites) <= 0 {
return nil
}
for _, favorite := range favorites {
if currentDBFavCount == e621User.FavoriteCount {
break
}
isFaved, err := graphConnection.CheckUserToPostLink(ctx, favorite.ID, e621User.ID)
if err != nil {
return err
}
if !isFaved {
err = uploadDataToDB(ctx, graphConnection, favorite, e621User)
if err != nil {
return err
}
currentDBFavCount++
}
}
pageIndex++
}
log.WithFields(log.Fields{
"e621_username": e621User.Name,
"e621_user_id": e621User.ID,
"post_amount": e621User.FavoriteCount,
"scrape_time": time.Since(scrapeTime),
}).Info("service: finished processing favorites")
return nil
}
func uploadDataToDB(ctx context.Context, graphConnection logic.GraphConnection, favorite model.Post, e621User model.User) error {
start := time.Now()
err := uploadNodes(ctx, graphConnection, favorite)
if err != nil {
return err
}
log.WithFields(log.Fields{
"e621_username": e621User.Name,
"e621_user_id": e621User.ID,
"post_id": favorite.ID,
"upload_time": time.Since(start),
}).Debug("service: uploaded post")
start = time.Now()
err = uploadPostToUserRelationship(ctx, graphConnection, favorite, e621User)
if err != nil {
log.Fatal(err)
return err
}
err = uploadSourceTagRelationship(ctx, graphConnection, favorite)
if err != nil {
log.Fatal(err)
return err
}
err = uploadGeneralTagRelationship(ctx, graphConnection, favorite)
if err != nil {
log.Fatal(err)
return err
}
err = uploadCharacterTagtRelationship(ctx, graphConnection, favorite)
if err != nil {
log.Fatal(err)
return err
}
err = uploadCopyrightTagRelationship(ctx, graphConnection, favorite)
if err != nil {
log.Fatal(err)
return err
}
err = uploadArtistTagRelationship(ctx, graphConnection, favorite)
if err != nil {
log.Fatal(err)
return err
}
log.WithFields(log.Fields{
"e621_username": e621User.Name,
"e621_user_id": e621User.ID,
"post_id": favorite.ID,
"upload_time": time.Since(start),
}).Debug("service: made relationship")
return nil
}
// uploadNodes uploads the post to the database and creates the nodes
func uploadNodes(ctx context.Context, graphConnection logic.GraphConnection, post model.Post) error {
uniqueGeneralTags := make([]string, 0)
uniqueCharacterTags := make([]string, 0)
uniqueCopyrightTags := make([]string, 0)
uniqueArtistTags := make([]string, 0)
allGeneralTags := make([]string, 0)
allCharacterTags := make([]string, 0)
allCopyrightTags := make([]string, 0)
allArtistTags := make([]string, 0)
allGeneralTags = append(allGeneralTags, post.Tags.General...)
allCharacterTags = append(allCharacterTags, post.Tags.Character...)
allCopyrightTags = append(allCopyrightTags, post.Tags.Copyright...)
allArtistTags = append(allArtistTags, post.Tags.Artist...)
uniqueGeneralTags = util.UniqueNonEmptyElementsOf(allGeneralTags)
uniqueCharacterTags = util.UniqueNonEmptyElementsOf(allCharacterTags)
uniqueCopyrightTags = util.UniqueNonEmptyElementsOf(allCopyrightTags)
uniqueArtistTags = util.UniqueNonEmptyElementsOf(allArtistTags)
err := graphConnection.UploadPost(ctx, post.ID)
if err != nil {
return err
}
// Uploads the source to the database
for _, source := range post.Sources {
err := graphConnection.UploadSource(ctx, source)
if err != nil {
return err
}
}
for _, uniqueGeneralTag := range uniqueGeneralTags {
err := graphConnection.UploadTag(ctx, uniqueGeneralTag, "general")
if err != nil {
return err
}
}
for _, uniqueCharacterTag := range uniqueCharacterTags {
err := graphConnection.UploadTag(ctx, uniqueCharacterTag, "character")
if err != nil {
return err
}
}
for _, uniqueCopyrightTag := range uniqueCopyrightTags {
err := graphConnection.UploadTag(ctx, uniqueCopyrightTag, "copyright")
if err != nil {
return err
}
}
for _, uniqueArtistTag := range uniqueArtistTags {
err := graphConnection.UploadTag(ctx, uniqueArtistTag, "artist")
if err != nil {
return err
}
}
return nil
}
// uploadPostToUserRelationship creates a relationship between the user and the post
func uploadPostToUserRelationship(ctx context.Context, graphConnection logic.GraphConnection, post model.Post, e621User model.User) error {
err := graphConnection.EstablishUserToPostLink(ctx, post.ID, e621User.ID)
if err != nil {
return err
}
// log.Printf("Created UserToPostRelationship for user: %s to post: %d", e621User.Name, post.ID)
return nil
}
// uploadSourceTagRelationship creates a relationship between the post and the source
func uploadSourceTagRelationship(ctx context.Context, graphConnection logic.GraphConnection, post model.Post) error {
for _, source := range post.Sources {
err := graphConnection.EstablishPostToSourceLink(ctx, post.ID, source)
if err != nil {
return err
}
// log.Printf("Created PostToSourceRelationship for Post: %d to source: %s", post.ID, source)
}
return nil
}
// uploadGeneralTagRelationship creates a relationship between the post and the general tag
func uploadGeneralTagRelationship(ctx context.Context, graphConnection logic.GraphConnection, post model.Post) error {
for _, generalTag := range post.Tags.General {
err := graphConnection.EstablishPostToTagLink(ctx, post.ID, generalTag)
if err != nil {
return err
}
// log.Printf("Created PostToTagRelationship for post: %d to general tag: %s", post.ID, generalTag)
}
return nil
}
// uploadCharacterTagtRelationship creates a relationship between the post and the character tag
func uploadCharacterTagtRelationship(ctx context.Context, graphConnection logic.GraphConnection, post model.Post) error {
for _, characterTag := range post.Tags.Character {
err := graphConnection.EstablishPostToTagLink(ctx, post.ID, characterTag)
if err != nil {
return err
}
// log.Printf("Created PostToTagRelationship for post: %d to character tag: %s", post.ID, characterTag)
}
return nil
}
// uploadCopyrightTagRelationship creates a relationship between the post and the copyright tag
func uploadCopyrightTagRelationship(ctx context.Context, graphConnection logic.GraphConnection, post model.Post) error {
for _, copyrightTag := range post.Tags.Copyright {
err := graphConnection.EstablishPostToTagLink(ctx, post.ID, copyrightTag)
if err != nil {
return err
}
// log.Printf("Created PostToTagRelationship for post: %d to copyright tag: %s", post.ID, copyrightTag)
}
return nil
}
// uploadArtistTagRelationship creates a relationship between the post and the artist tag
func uploadArtistTagRelationship(ctx context.Context, graphConnection logic.GraphConnection, post model.Post) error {
for _, artistTag := range post.Tags.Artist {
err := graphConnection.EstablishPostToTagLink(ctx, post.ID, artistTag)
if err != nil {
return err
}
// log.Printf("Created PostToTagRelationship for post: %d to artist tag: %s", post.ID, artistTag)
}
return nil
}

4
main.go Normal file
View File

@ -0,0 +1,4 @@
package main
func main() {
}

79
pkg/e621/builder/dmail.go Normal file
View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

47
pkg/e621/builder/note.go Normal file
View File

@ -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 &note, nil
}

View File

@ -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)
}

98
pkg/e621/builder/notes.go Normal file
View File

@ -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
}

View File

@ -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)
}

47
pkg/e621/builder/pool.go Normal file
View File

@ -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
}

View File

@ -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)
}

114
pkg/e621/builder/pools.go Normal file
View File

@ -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
}

View File

@ -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)
}

47
pkg/e621/builder/post.go Normal file
View File

@ -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
}

View File

@ -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)
}

85
pkg/e621/builder/posts.go Normal file
View File

@ -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
}

View File

@ -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)
}

47
pkg/e621/builder/tag.go Normal file
View File

@ -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
}

View File

@ -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)
}

103
pkg/e621/builder/tags.go Normal file
View File

@ -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
}

46
pkg/e621/builder/user.go Normal file
View File

@ -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
}

View File

@ -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)
}

127
pkg/e621/builder/users.go Normal file
View File

@ -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
}

View File

@ -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)
}

244
pkg/e621/client.go Normal file
View File

@ -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)
}

View File

@ -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
}

234
pkg/e621/endpoints/dmail.go Normal file
View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

121
pkg/e621/endpoints/note.go Normal file
View File

@ -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(&noteResponse)
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, &notesResponse)
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
}

View File

@ -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)
}

114
pkg/e621/endpoints/pool.go Normal file
View File

@ -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
}

View File

@ -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)
}

113
pkg/e621/endpoints/post.go Normal file
View File

@ -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
}

View File

@ -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)
}

125
pkg/e621/endpoints/tag.go Normal file
View File

@ -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
}

View File

@ -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)
}

114
pkg/e621/endpoints/user.go Normal file
View File

@ -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
}

View File

@ -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)
}

15
pkg/e621/model/basic.go Normal file
View File

@ -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
}

16
pkg/e621/model/dmail.go Normal file
View File

@ -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"`
}

17
pkg/e621/model/note.go Normal file
View File

@ -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"`
}

30
pkg/e621/model/pool.go Normal file
View File

@ -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"`
}

93
pkg/e621/model/post.go Normal file
View File

@ -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"`
}

26
pkg/e621/model/tag.go Normal file
View File

@ -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"`
}

51
pkg/e621/model/user.go Normal file
View File

@ -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"`
}

View File

@ -0,0 +1,4 @@
package utils
// E621_MAX_POST_COUNT is the maximum allowable post count for E621.
const E621_MAX_POST_COUNT = 320

106
pkg/e621/utils/error.go Normal file
View File

@ -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"
}

28
pkg/e621/utils/test.go Normal file
View File

@ -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
}

View File

@ -1,19 +0,0 @@
package logic
import (
"context"
"git.dragse.it/anthrove/e621-sdk-go/pkg/e621/model"
)
type GraphConnection interface {
Connect(ctx context.Context, endpoint string, username string, password string) error
UploadUser(ctx context.Context, user model.User) error
UploadSource(ctx context.Context, SourceURL string) error
UploadPost(ctx context.Context, e621ID model.PostID) error
UploadTag(ctx context.Context, name string, tagType string) error
EstablishPostToTagLink(ctx context.Context, e621PostID model.PostID, e621Tag string) error
EstablishPostToSourceLink(ctx context.Context, e621PostID model.PostID, sourceURL string) error
EstablishUserToPostLink(ctx context.Context, e621PostID model.PostID, e621UserID model.UserID) error
CheckUserToPostLink(ctx context.Context, e621PostID model.PostID, e621UserID model.UserID) (bool, error)
GetUserFavoriteCount(ctx context.Context, userID model.UserID) (int64, error)
}

View File

@ -1,27 +0,0 @@
package util
// UniqueNonEmptyElementsOf returns a new slice containing unique non-empty elements from the input slice.
// It removes duplicate elements and empty strings while preserving the order of appearance.
func UniqueNonEmptyElementsOf(s []string) []string {
// Create a map to store unique elements
unique := make(map[string]bool)
// Create a new slice to store the unique non-empty elements
us := make([]string, 0, len(s))
for _, elem := range s {
// Skip empty strings
if len(elem) == 0 {
continue
}
// Check if the element is already in the unique map
if !unique[elem] {
// Add the element to the unique map and the new slice
unique[elem] = true
us = append(us, elem)
}
}
return us
}

View File

@ -1,44 +0,0 @@
package queue
import (
"errors"
log "github.com/sirupsen/logrus"
)
type Queue struct {
tasks []SchedulerTask
notifyChannel chan bool
}
func NewQueue() Queue {
return Queue{
notifyChannel: make(chan bool),
}
}
// WaitForElement need to be called everytime before Popping an Element!
// Also, it is required to have this function called in a separate go-routine because Push use the NotifyChannel in the
// routine. So the thread waits, till WaitForElement pulls the Item from the channel
// FIXME this should be fixed in future. require more discussion what is the best way.
func (queue *Queue) WaitForElement() {
log.Debug("queue: waiting for element")
_ = <-queue.notifyChannel
}
func (queue *Queue) Pop() (SchedulerTask, error) {
if len(queue.tasks) == 0 {
return nil, errors.New("try to remove an element of a empty queue")
}
task := queue.tasks[0]
queue.tasks = queue.tasks[1:]
return task, nil
}
func (queue *Queue) Push(task SchedulerTask) error {
if task == nil {
return errors.New("try to add task but task is empty")
}
queue.tasks = append(queue.tasks, task)
queue.notifyChannel <- true
return nil
}

View File

@ -1,107 +0,0 @@
package queue
import (
"net/http"
"reflect"
"testing"
)
type schedulerTaskImplDummy struct {
}
func (s schedulerTaskImplDummy) BasicAuth() (string, string) {
return "", ""
}
func (s schedulerTaskImplDummy) UriPath() string {
return ""
}
func (s schedulerTaskImplDummy) SendError(_ error) {
return
}
func (s schedulerTaskImplDummy) SendStatusCode(_ int) {
return
}
func (s schedulerTaskImplDummy) SendResponse(_ *http.Response) {
return
}
func TestQueue_Pop(t *testing.T) {
type fields struct {
elements []SchedulerTask
}
tests := []struct {
name string
fields fields
want SchedulerTask
wantErr bool
}{
{
name: "Pop element of empty list",
fields: fields{},
want: nil,
wantErr: true,
},
{
name: "Pop element of a filled list with three elements",
fields: fields{elements: []SchedulerTask{
schedulerTaskImplDummy{},
schedulerTaskImplDummy{},
schedulerTaskImplDummy{},
}},
want: schedulerTaskImplDummy{},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
queue := &Queue{
tasks: tt.fields.elements,
}
got, err := queue.Pop()
if (err != nil) != tt.wantErr {
t.Errorf("Pop() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Pop() got = %v, want %v", got, tt.want)
}
})
}
}
func TestQueue_Push(t *testing.T) {
t.Run("Push tasks to empty queue", func(t *testing.T) {
queue := Queue{tasks: []SchedulerTask{}, notifyChannel: make(chan bool)}
go queue.WaitForElement()
task := schedulerTaskImplDummy{}
err := queue.Push(task)
if err != nil {
t.Errorf("Push() error = %v", err)
}
if len(queue.tasks) != 1 {
t.Errorf("Push() error = queue is not one")
}
if queue.tasks[0] != task {
t.Errorf("Push() error = wrong queue task in queue")
}
})
t.Run("Push tasks to filled queue", func(t *testing.T) {
queue := Queue{tasks: []SchedulerTask{schedulerTaskImplDummy{}}, notifyChannel: make(chan bool)}
go queue.WaitForElement()
task := schedulerTaskImplDummy{}
err := queue.Push(task)
if err != nil {
t.Errorf("Push() error = %v", err)
}
if len(queue.tasks) != 2 {
t.Errorf("Push() error = queue is not two")
}
if queue.tasks[1] != task {
t.Errorf("Push() error = wrong queue task in queue")
}
})
}

View File

@ -1,13 +0,0 @@
package queue
import (
"net/http"
)
type SchedulerTask interface {
UriPath() string
SendStatusCode(statusCode int)
SendError(err error)
SendResponse(response *http.Response)
BasicAuth() (string, string)
}

View File

@ -1 +0,0 @@
docker run -it -p 7687:7687 -p 7444:7444 -p 3000:3000 -v mg_lib:/var/lib/memgraph memgraph/memgraph-platform

15
tests/note.json Normal file
View File

@ -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"
}

1127
tests/notes.json Normal file

File diff suppressed because it is too large Load Diff

38
tests/pool.json Normal file
View File

@ -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
}

4002
tests/pools.json Normal file

File diff suppressed because it is too large Load Diff

111
tests/post.json Normal file
View File

@ -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
}
}

Some files were not shown because too many files have changed in this diff Show More