initial commit

This commit is contained in:
SoXX 2023-10-16 15:56:00 +02:00
commit 3da230bf58
18 changed files with 710 additions and 0 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

189
.gitignore vendored Normal file
View File

@ -0,0 +1,189 @@
# 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
### Go ###
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
# Go workspace file
go.work
### GoLand+all ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### GoLand+all Patch ###
# Ignore everything but code style settings and run configurations
# that are supposed to be shared within teams.
.idea/*
!.idea/codeStyles
!.idea/runConfigurations
### Linux ###
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### macOS Patch ###
# iCloud generated files
*.icloud
### Windows ###
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
# End of https://www.toptal.com/developers/gitignore/api/windows,linux,goland+all,macos,go

43
README.md Normal file
View File

@ -0,0 +1,43 @@
# Go-e621 SDK
An unofficial e621 SDK (Client) library to interact with **e621**, **e923** and **e6ai**. Maintained by the Anthrove Development Team.
# 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 | :x: | |
| Tags | :x: | |
| Tag aliases | :x: | |
| Tag implications | :x: | |
| Notes | :x: | |
| Pools | :x: | |
| Users | :x: | |
| Favorites | :x: | |
| DB export | :x: | |
_Low Level API_
| Area | Complete | Comment |
|:-----------------|:------------------:|:--------|
| Authentication | :x: | |
| Posts | :x: | |
| Tags | :x: | |
| Tag aliases | :x: | |
| Tag implications | :x: | |
| Notes | :x: | |
| Pools | :x: | |
| Users | :heavy_check_mark: | |
| Favorites | :x: | |
| DB export | :x: | |

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

@ -0,0 +1,39 @@
package main
import (
"git.dragse.it/anthrove/e621-to-graph/pkg/e621/endpoints"
"git.dragse.it/anthrove/e621-to-graph/pkg/e621/model"
"log"
"net/http"
)
func main() {
requestContext := model.RequestContext{
Host: "https://e621.net",
UserAgent: "Go-e621-SDK (@username)",
Username: "",
APIKey: "",
}
log.Println("Getting single user: ")
client := http.Client{}
user, err := endpoints.GetUser(client, requestContext, "selloo")
if err != nil {
log.Println(err)
}
log.Println(user.ID)
log.Println("----------")
log.Println("Getting list of users: ")
query := map[string]string{
"limit": "5",
}
userList, err := endpoints.GetUsers(client, requestContext, query)
if err != nil {
log.Println(err)
}
log.Println(len(userList))
log.Println("----------")
}

8
go.mod Normal file
View File

@ -0,0 +1,8 @@
module git.dragse.it/anthrove/e621-to-graph
go 1.21.3
require (
github.com/google/go-cmp v0.6.0 // indirect
github.com/jarcoal/httpmock v1.3.1 // indirect
)

4
go.sum Normal file
View File

@ -0,0 +1,4 @@
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww=
github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=

1
internal/client.go Normal file
View File

@ -0,0 +1 @@
package internal

105
internal/utils/error.go Normal file
View File

@ -0,0 +1,105 @@
package utils
import "fmt"
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("unhandels status code: %d", statusCode)
}
return err
}
type AccessDeniedError struct {
}
func (_ AccessDeniedError) Error() string {
return "access denied"
}
type NotFoundError struct {
}
func (_ NotFoundError) Error() string {
return "not found"
}
type PreconditionFailedError struct {
}
func (_ PreconditionFailedError) Error() string {
return "precondition failed"
}
type RateLimitReachedError struct {
}
func (_ RateLimitReachedError) Error() string {
return "rate limit reached"
}
type InvalidParametersError struct {
}
func (_ InvalidParametersError) Error() string {
return "invalide parameters"
}
type InternalServerError struct {
}
func (_ InternalServerError) Error() string {
return "internal server error"
}
type BadGatewayError struct {
}
func (_ BadGatewayError) Error() string {
return "bad gateway"
}
type ServiceUnavailableError struct {
}
func (_ ServiceUnavailableError) Error() string {
return "service unavailable"
}
type UnknownError struct {
}
func (_ UnknownError) Error() string {
return "unknown error"
}
type OriginConnectionTimeOutError struct {
}
func (_ OriginConnectionTimeOutError) Error() string {
return "origin connection time-out"
}
type SSLHandshakeFailedError struct {
}
func (_ SSLHandshakeFailedError) Error() string {
return "ssl handshake failed"
}

1
main.go Normal file
View File

@ -0,0 +1 @@
package e621_sdk_go

View File

@ -0,0 +1 @@
package endpoints

View File

@ -0,0 +1 @@
package endpoints

View File

@ -0,0 +1 @@
package endpoints

View File

@ -0,0 +1,4 @@
package endpoints
// Get all users (); endpint to use is e621.net/users.json
// Get specific user

View File

@ -0,0 +1 @@
package endpoints

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

@ -0,0 +1,103 @@
package endpoints
import (
"encoding/json"
"fmt"
"git.dragse.it/anthrove/e621-to-graph/internal/utils"
"git.dragse.it/anthrove/e621-to-graph/pkg/e621/model"
"log"
"net/http"
)
// GetUser sends an HTTP GET request to fetch user information from e621.net.
// It constructs a URL based on the provided 'host' and 'username'.
// The response is returned as a *http.Response pointer.
func GetUser(client http.Client, 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.
log.Println(err)
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 := client.Do(r)
if err != nil {
// Log the error and return an empty User struct and the error.
log.Println(err)
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.
log.Println(err)
return model.User{}, err
}
// Return the user information and no error (nil).
return user, nil
}
// GetUsers sends an HTTP GET request to fetch user data from e621.net.
// It constructs a URL based on the provided 'host' and 'query' parameters.
// The response is returned as a *http.Response pointer.
func GetUsers(client http.Client, 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 {
log.Print(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 := client.Do(r)
if err != nil {
log.Print(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.
log.Println(err)
return []model.User{}, err
}
return user, nil
}

View File

@ -0,0 +1,157 @@
package endpoints
import (
"git.dragse.it/anthrove/e621-to-graph/pkg/e621/model"
"github.com/jarcoal/httpmock"
"net/http"
"testing"
)
func TestGetUser(t *testing.T) {
httpmock.Activate()
defer httpmock.DeactivateAndReset()
userResponse := model.User{
WikiPageVersionCount: 0,
ArtistVersionCount: 0,
PoolVersionCount: 0,
ForumPostCount: 0,
CommentCount: 69,
FlagCount: 0,
FavoriteCount: 1337,
PositiveFeedbackCount: 0,
NeutralFeedbackCount: 0,
NegativeFeedbackCount: 0,
UploadLimit: 0,
ID: 1,
CreatedAt: "2020-04-07T07:16:40.286+02:00",
Name: "MaxMustermannDer69ste",
Level: 0,
BaseUploadLimit: 0,
PostUploadCount: 0,
PostUpdateCount: 0,
NoteUpdateCount: 0,
IsBanned: false,
CanApprovePosts: false,
CanUploadFree: false,
LevelString: "Member",
AvatarID: 7,
}
jsonResponser, err := httpmock.NewJsonResponder(200, userResponse)
if err != nil {
t.Error(err)
return
}
httpmock.RegisterResponder("GET", "https://e621.net/users/MaxMustermannDer69ste.json", jsonResponser)
requestContext := model.RequestContext{
Host: "https://e621.net",
UserAgent: "Go-e621-SDK (@username)",
Username: "memo",
APIKey: "123456",
}
client := http.Client{}
user, err := GetUser(client, requestContext, "MaxMustermannDer69ste")
if err != nil {
t.Error(err)
return
}
if user.ID == userResponse.ID && user.Name == userResponse.Name && user.CreatedAt == user.CreatedAt {
return
}
t.Errorf("Respons did not match mock data:\nOriginal: %v\nMock: %v", user, userResponse)
}
func TestGetUsers(t *testing.T) {
httpmock.Activate()
defer httpmock.DeactivateAndReset()
userResponse := []model.User{
{
WikiPageVersionCount: 0,
ArtistVersionCount: 0,
PoolVersionCount: 0,
ForumPostCount: 0,
CommentCount: 69,
FlagCount: 0,
FavoriteCount: 1337,
PositiveFeedbackCount: 0,
NeutralFeedbackCount: 0,
NegativeFeedbackCount: 0,
UploadLimit: 0,
ID: 1,
CreatedAt: "2020-04-07T07:16:40.286+02:00",
Name: "MaxMustermannDer69ste",
Level: 0,
BaseUploadLimit: 0,
PostUploadCount: 0,
PostUpdateCount: 0,
NoteUpdateCount: 0,
IsBanned: false,
CanApprovePosts: false,
CanUploadFree: false,
LevelString: "Member",
AvatarID: 7,
},
{
WikiPageVersionCount: 0,
ArtistVersionCount: 0,
PoolVersionCount: 0,
ForumPostCount: 0,
CommentCount: 1337,
FlagCount: 0,
FavoriteCount: 69,
PositiveFeedbackCount: 0,
NeutralFeedbackCount: 0,
NegativeFeedbackCount: 0,
UploadLimit: 0,
ID: 1,
CreatedAt: "2020-04-08T07:16:40.286+02:00",
Name: "HollaDieWaldfee",
Level: 0,
BaseUploadLimit: 0,
PostUploadCount: 0,
PostUpdateCount: 0,
NoteUpdateCount: 0,
IsBanned: false,
CanApprovePosts: false,
CanUploadFree: false,
LevelString: "Member",
AvatarID: 16,
},
}
jsonResponser, err := httpmock.NewJsonResponder(200, userResponse)
if err != nil {
t.Error(err)
return
}
httpmock.RegisterResponder("GET", "https://e621.net/users.json", jsonResponser)
requestContext := model.RequestContext{
Host: "https://e621.net",
UserAgent: "Go-e621-SDK (@username)",
Username: "memo",
APIKey: "123456",
}
client := http.Client{}
user, err := GetUsers(client, requestContext, map[string]string{})
if err != nil {
t.Error(err)
return
}
if len(user) == 2 && user[0].ID == userResponse[0].ID && user[1].Name == userResponse[1].Name {
return
}
t.Errorf("Respons did not match mock data:\nOriginal: %v\nMock: %v", user, userResponse)
}

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

@ -0,0 +1,8 @@
package model
type RequestContext struct {
Host string
UserAgent string
Username string
APIKey string
}

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

@ -0,0 +1,28 @@
package model
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 int64 `json:"id"`
CreatedAt string `json:"created_at"`
Name string `json:"name"`
Level int64 `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 interface{} `json:"avatar_id"`
}